1. 程式人生 > >【做題】SDOI2017蘋果樹——dfs序的運用

【做題】SDOI2017蘋果樹——dfs序的運用

void push_back 錯誤 鏈接 合並 while 它的 push .html

原文鏈接 https://www.cnblogs.com/cly-none/p/9845046.html

題意:給出一棵\(n\)個結點的樹,在第\(i\)個結點上有\(a_i\)個權值為\(v_i\)的物品。\(1\)號結點是根結點。你需要選出若幹個物品(設選了\(t\)個),滿足:

  • 如果選了結點\(i\)上的物品,那麽\(i\)到根的鏈上每個結點都至少要選一個物品。
  • 設有選取物品的結點的最大深度為\(h\),那麽\(t \leq h + k\)\(k\)為一個給定的常數。

在此基礎上,你需要最大化所選的物品的權值和。
\(n \leq 2 \times 10^4, \, k \leq 5 \times 10^5, \, n \times k \leq 2.5 \times 10^7\)

顯然,最終做法的復雜度應該是\(O(nk)\)的。

但這個問題比較復雜,直接想比較困難。因此,我們先考慮問題的簡化版。

問題1

當第二個條件改為\(t \leq k\)時,怎麽做?

對於這種一個結點的決策影響其子樹的問題,我們可以對dfs序倒過來dp。確切地說,考慮當前是\(i\),那麽\(i\)的子樹就是\(dfn_i\)之後的一段連續區間。那麽,把dfs序倒過來後,結點\(i\)就有兩種可能:

  • 選了\(i\)上的物品。就是一個多重背包,從\(dp_{dfn_i + 1}\)上更新過來。
  • 不選\(i\)上的物品。那\(i\)子樹中的所有物品都不能選。從\(dp_{dfn_i + sz_i}\)
    上更新過來。

用單調隊列優化多重背包後,就能做到\(O(nk)\)


然而,回過頭來,我們依舊對\(t \leq h + k\)感到棘手。嘗試按常規方法dp對\(k+h-t\)記錄答案,但沒有用。這個限制其實就在於,選出一條一段是根結點的鏈,鏈上每個點都取一個不計入\(t\)的物品。我們設這條鏈除\(1\)外的端點為\(x\)。考慮\(\forall i, \, a_i = 1\)的部分分。那麽,假如我們已經確定了\(x\),則剩下的答案就是刪去\(1\)\(x\)的鏈,對剩下的森林做問題1的結果。

因此,我們可以考慮下面這個問題:

問題2

預處理:對於所有\(x\),刪去\(x\)到根的路徑後剩下的森林的問題1的答案。

博主認為,這個問題的解法相當有趣,也挺難想到的。

考慮剩下的森林的一半就是在dfs序上,從\(dfn_i + 1\)\(n\)的一段區間(包括了\(i\)的子樹)。這個部分我們在dp時就已經把答案求出來了。然而,另一部分在dfs序上既不是一段後綴,也不是連續的區間。\([1,dfn_i-1]\)中還混入了\(i\)的所有祖先。

因此,我們把這棵樹左右翻轉,把剩下森林的兩半交換位置。也就是,再生成一個dfs序,但每個結點反序訪問它的孩子結點。這樣,我們就把森林的另一部分也表示為了dfs序的一個後綴。值得註意的是,\(i\)的子樹不能算兩次,所以這個後綴應該是[dfn_i + sz_i,n]。

這樣,我們做出兩個dfs序,對每個做問題1的dp,就能解決此問題。


然後就是處理\(a_i \neq 1\)的情況。上面的算法會錯誤,就在於\(x\)到根的路徑上的結點,可能選了多個物品。那麽,我們就對每個結點\(i\)建一個輔助點\(i'\),存放了\(a_i - 1\)個原來在\(i\)上的物品。這樣,對於任何一個非輔助結點,它到根的路徑上所有點都只有一個物品。

這樣就能把最終問題轉化為問題1,\(O(nk)\)地解決本題。

#include <bits/stdc++.h>
using namespace std;
const int N = 40010, K = 500010, SIZE = 51000010;
int n,k,val[N],num[N],dfn[N],sz[N],fa[N],cnt,dis[N],ans,rec[N],spadp[SIZE],spag[SIZE];
vector<int> ch[N];
int *dp[N],*g[N];
void dfs(int pos) {
  sz[pos] = 1;
  for (int i = 0 ; i < (int)ch[pos].size() ; ++ i) {
    dfs(ch[pos][i]);
    sz[pos] += sz[ch[pos][i]];
  }
  dfn[rec[pos] = ++cnt] = pos;
}
void fsd(int pos) {
  dis[pos] += val[pos];
  for (int i = (int)ch[pos].size() - 1 ; i >= 0 ; -- i) {
    dis[ch[pos][i]] = dis[pos];
    fsd(ch[pos][i]);
  }
  dfn[++cnt] = pos;
}
void update(int las,int cur) {
  static int q[K],l,r;
  l = 1, r = 0;
  q[++r] = 0;
  for (int i = 1 ; i <= k ; ++ i) {
    while (l <= r && i - q[l] > num[dfn[cur]])
      ++ l;
    if (l <= r)
      dp[cur][i] = dp[las][q[l]] + val[dfn[cur]] * (i - q[l]);
    else dp[cur][i] = 0;
    while (l <= r && dp[las][i] > dp[las][q[r]] + val[dfn[cur]] * (i - q[r]))
      -- r;
    q[++r] = i;
  }
}
void init() {
  ans = 0;
  for (int i = 0 ; i <= 2 * n ; ++ i) {
    ch[i].clear();
    dp[i] = spadp + i * (k + 1);
    g[i] = spag + i * (k + 1);
    memset(dp[i],0,sizeof(int) * (k + 1));
    memset(g[i],0,sizeof(int) * (k + 1));
  }
  dis[1] = 0;
}
int main() {
  int T;
  scanf("%d",&T);
  while (T --) {
    scanf("%d%d",&n,&k);
    init();
    for (int i = 1 ; i <= n ; ++ i)
      scanf("%d%d%d",&fa[i],&num[i],&val[i]);
    for (int i = 2 ; i <= n ; ++ i)
      ch[fa[i]].push_back(i);
    for (int i = 1 ; i <= n ; ++ i) {
      ch[i].push_back(i+n);
      val[i+n] = val[i];
      num[i+n] = num[i] - 1;
      num[i] = 1;
    }
    cnt = 0;
    dfs(1);
    for (int i = 1 ; i <= 2 * n ; ++ i) {
      update(i-1,i);
      for (int j = 1 ; j <= k ; ++ j)
        dp[i][j] = max(dp[i][j],dp[i - sz[dfn[i]]][j]), dp[i][j] = max(dp[i][j],dp[i][j-1]);
    }
    for (int i = 1 ; i <= 2 * n ; ++ i)
      for (int j = 1 ; j <= k ; ++ j)
    g[i][j] = dp[i][j];
    cnt = 0;
    fsd(1);
    for (int i = 1 ; i <= 2 * n ; ++ i) {
      update(i-1,i);
      for (int j = 1 ; j <= k ; ++ j)
    dp[i][j] = max(dp[i][j],dp[i - sz[dfn[i]]][j]), dp[i][j] = max(dp[i][j],dp[i][j-1]);
    }
    for (int i = 1 ; i <= 2 * n ; ++ i) {
      if (dfn[i] > n) continue;
      int p = rec[dfn[i]] - sz[dfn[i]];
      for (int j = 0 ; j <= k ; ++ j)
    ans = max(ans,dis[dfn[i]] + dp[i-1][j] + g[p][k-j]);
    }
    printf("%d\n",ans);
  }
  return 0;
}



小結:一道對dfs序上dp進行拓展的好題。當一個問題分成了性質相同的兩半,而前者容易解決,後者難以解決的問題時,尋找方式來交換這兩部分的位置,最後合並。這個思路應該記住。

【做題】SDOI2017蘋果樹——dfs序的運用