1. 程式人生 > >SDOI2017 蘋果樹

SDOI2017 蘋果樹

har tchar 枚舉 另一個 做的 蘋果樹 clas ace 節點

SDOI2017 蘋果樹

題意:

題目傳送門

題解:

好神仙的題啊……
考慮如果沒有題目中的\(t - h \leq k\)的限制,那麽這道題目就是樹上依賴多重背包,復雜度為\(O(nk^2)\),然後用單調隊列優化就可以達到\(O(nk)\),這個是比較好做的。然後我們考慮如何處理題目中給出的限制,實際上,題目中的限制可以轉化成這樣:假設取一個蘋果的代價是\(1\),那麽我們可以花費\(0\)的代價取一條從根開始的鏈上的每個點各一個蘋果,之後每取一個蘋果都會產生\(1\)的代價,最終總代價不能超過\(k\)
為什麽這個轉化是正確的呢?考慮我們取了一條鏈上每個點各一個蘋果之後,最大深度\(h\)

等於取的蘋果個數\(t\),所以這部分相當與是沒有代價的。然後我們考慮枚舉這條鏈之後,整棵樹變成了什麽樣。大致分成四個部分:

  1. 在鏈上並且是免費取的部分
  2. 在鏈上並且但是需要付出代價的部分
  3. 在鏈的左邊的部分
  4. 在鏈的右邊的部分

然後我們發現這棵樹除了鏈之外被分成了兩個部分,而在鏈右邊的部分實際上是比較容易計算的,因為容易發現這部分在\(dfn\)序上是連續的一段,我們可以直接處理出來。至於左邊的部分,由於他們\(dfn\)序不是連續的,我們似乎無從下手。但是反過來考慮,我們將邊表\(reverse\)一下,那麽再次求一邊\(dfn\)序之後,鏈的左右兩半部分會交換過來,那麽原本的左半部分的\(dp\)

值就變得好求了。這樣我們記\(dp[i][j]\)表示\(dfn\)序在\([dfn[i] + 1,n]\)這個區間內,取\(k\)個蘋果的最大價值,這個就可以在\(O(nk)\)的時間處理出來。然後我們考慮如何處理在鏈上並且需要付出代價部分的值,一種方法就是我們可以再次對鏈上進行一次多重背包,但是有一種更加簡單的做法。我們將樹上的節點都拆成兩個節點,一個節點蘋果個數為\(1\),連原圖中的邊,另一個節點蘋果個數為\(a_i - 1\),只向這個點原來的節點連邊。容易證明這樣的轉化也是正確的而拆了點之後,鏈上的點的貢獻也被算到3.4兩個部分中去了,就不用單獨計算了。枚舉選擇的鏈,以及鏈右邊選了幾個蘋果,最後答案就是這四個部分的權值之和。不過這題的時間空間都似乎挺卡的,我似乎開了\(O2\)
才卡著過去的……

Code

#pragma GCC optimize(2,"inline","Ofast")
#include <bits/stdc++.h>
using namespace std;
const int N = 4e4 + 50;
const int M = 5e5 + 50;

int Q, n, clck, k;
int dfn[N], a[N], v[N], sz[N], id[N], Mv[N];
vector<int> f[N], t[N];
vector<int> G[N];

void read(int &x) {
  x = 0; int f = 1; char ch = getchar();
  while(ch < '0' || ch > '9') {if(ch == '=') f = -1; ch = getchar();}
  while(ch >='0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
  x *= f;
}

void clear(vector<int> &x) { vector<int> tmp; swap(tmp, x);}
void Init() {
  for(int i = 0; i <= n * 2; i++) {
    clear(G[i]); clear(f[i]); clear(t[i]);
  }
}

void Dfs(int o, int V, int tp) {
  sz[o] = 1; V += v[o]; Mv[o] = V;
  for(int i = 0; i < (int)G[o].size(); i++) {
    int to = G[o][i];
    Dfs(to, V, tp);
    sz[o] += sz[to];
  }
  if(tp == 0) dfn[id[o] = ++clck] = o;
  else dfn[++clck] = o;
}

void Dp(int x, int y) {
  static int que[M];
  int l = 1, r = 0;
  que[++r] = 0;
  for(int i = 1; i <= k; i++) {
    while(l <= r && i - que[l] > a[dfn[y]]) ++l;
    if(l <= r) t[y][i] = t[x][que[l]] + v[dfn[y]] * (i - que[l]);
    else t[y][i] = 0;
    while(l <= r && t[x][i] >= t[x][que[r]] + (i - que[r]) * v[dfn[y]]) --r;
    que[++r] = i;
  }
}

int main() {
  read(Q);
  while(Q--) {
    Init();
    read(n); read(k);
    for(int i = 1, x; i <= n; i++) {
      read(x); read(a[i]); read(v[i]);
      if(x) G[x].push_back(i);
    }
    for(int i = 1; i <= n; i++) {
      v[i + n] = v[i];
      a[i + n] = a[i] - 1;
      a[i] = 1;
      G[i].push_back(i + n);
    }
    clck = 0;
    Dfs(1, 0, 0);
    for(int i = 0; i <= n * 2; i++) f[i].resize(k + 5), t[i].resize(k + 5);
    for(int i = 1; i <= n * 2; i++) {
      Dp(i - 1, i);
      for(int j = 0; j <= k; j++) {
        t[i][j] = max(t[i][j], t[i - sz[dfn[i]]][j]);
        if(j) t[i][j] = max(t[i][j], t[i][j - 1]);
      }
    }
    swap(f, t);
    for(int i = 1; i <= n * 2; i++) reverse(G[i].begin(), G[i].end());
    clck = 0;
    Dfs(1, 0, 1);
    for(int i = 1; i <= n * 2; i++) {
      Dp(i - 1, i);
      for(int j = 0; j <= k; j++) {
        t[i][j] = max(t[i][j], t[i - sz[dfn[i]]][j]);
        if(j) t[i][j] = max(t[i][j], t[i][j - 1]);
      }
    }
    int ans = 0;
    for(int i = 1; i <= n * 2; i++) {
      if(dfn[i] > n) continue;
      int pos = id[dfn[i]] - sz[dfn[i]];
      for(int j = 0; j <= k; j++) {
        ans = max(ans, Mv[dfn[i]] + t[i - 1][j] + f[pos][k - j]);
      }
    }
    printf("%d\n", ans);
  }
  return 0;
}

SDOI2017 蘋果樹