[SDOI2017]苹果树
[SDOI2017]苹果树 解题报告
发现了几个很妙的处理方式,总结一下。
题意是在树上做一个多重背包,如果这个点选择了至少一个,父节点至少要选一个。同时允许在一条到根的链上每个点免费选一个。
我觉得最妙的地方是在处理那条免费链上。以前我见过的在树上dp状态都类似于这个子树选了什么,这道题就不一样了。因为dfs是有个顺序的,如果把先dfs到的画在左边,后dfs到的画在右边,那一条到叶子的链会把这棵树分为左右两半,就可以考虑像处理前缀和后缀那样来设状态。
具体来说,设 $F[x][i]$ 表示在dfs序上考虑到了点 $x$,其中 $x$ 到根的链上最多选了$a_i-1$个,一共选择了 $i$ 个物品所得到的最大价值;设 $G[x][i]$ 表示在反着dfs得到的dfs序上考虑到点 $x$ ,其中 $x$ 到根的链上一个都不选,一共选择了 $i$ 个的最大价值。这两个定义是差不多对称的,而且还要满足除了 $x$ 到根的这条链上的点,其他所有的点如果子节点选了,自己也一定选了。
这样设的好处是我们对每个叶子 $x$,可以直接用 $F[x][i]+G[x][k-i]+S_x$ 来更新答案,其中 $S_x$ 表示 $x$ 到根路径上点的价值和。一定是满足父节点限制的,因为除了 $x$ 到根的点,其他点都满足了,而现在强制选了 $x$ 到根的链。
具体考虑怎么dp。应该是有两种写法。
第一种是直接一边dfs一边dp。先说对 $F$ 。如果现在处于 $x$,要递归子节点 $y$。那就把 $x$ 的背包复制到 $y$,然后在 $y$ 时,首先把自己的 $a_y-1$ 个尝试去更新背包(为什么减一见后文),继续递归,最后回到 $x$ 时,需要决策是否选择 $y$ 及其子树中可能选的那些。因为现在 $y$ 的背包中已经决策过它的子节点们,而背包里选了些什么,具体选了几个我们是不知道的,但我们知道如果我们选了 $y$,$y$ 这个物品就至少得选 $1$ 个,才能满足了父节点限制。用 $F[y][i-1]+v_y$ 去更新 $F[x][i]$。
对于 $G$,对父节点限制的解决方法是一样的。与 $F$ 的区别仅在于我们要求这条链是不选的。又因为我们更新完答案之后也就可以破坏这个性质,所以如果没有子节点,更新答案;有子节点就在考虑完子节点后用 $a_x-1$ 个物品去更新背包。
这种对父节点至少选一个的解决方法是也是我觉得很妙的地方。
第二种写法就是先求出后缀dfs序,然后在上面dp。这种方法应该会常数小一点。回头再考虑一下第一种,您(其实是我)也许有疑问,这样用 $x$ 更新 $y$,回来又拿 $y$ 更新 $x$ ,不会不满足无后效性吗?
但稍加思考,我们状态的定义是在 dfs 序上,其实是子节点有了一个很新的版本,然后父节点发现自己过时了,拿过来先把自己更新一下,再去更新下一个子节点罢了。
如果我们直接从dfs的上一个点拉过来,不是更省事exp(/youl)...我感觉这就是在dfs序上直接dp的方法...没写,说错了别打我/kk。
最后还有一个要注意的是,每个背包是 $k+1$ 大的...所以空间是 $n(k+1)$,只开 $nk$ 会 TLE+RE...
代码
1 #include <cstdio> 2 #include <vector> 3 #include <cstring> 4 using namespace std; 5 int const maxn = 20003, maxk = 500003, NK = 25020003; 6 inline int read() { 7 int x = 0, c = 0; 8 while (c < '0' || '9' < c) c = getchar(); 9 while ('0' <= c && c <= '9') x = 10 * x + c - '0', c = getchar(); 10 return x; 11 } 12 int n = 0, k = 0; 13 int a[maxn], v[maxn], fa[maxn]; 14 int head[maxn], nxt[maxn]; 15 inline void add(int u, int e) { nxt[e] = head[u]; head[u] = e; } 16 int F[NK], G[NK]; 17 inline int index(int i, int j) { return (i - 1) * (k + 1) + j; } 18 int ans = 0; 19 inline void bag(int *f, int A, int V) { 20 if (A <= 0) return; 21 static int q[maxk], p[maxk]; 22 int ql = 1, qr = 0; 23 for (int i = 0; i <= k; ++i) { 24 f[i] -= i * V; 25 while (ql <= qr && f[q[qr]] <= f[i]) --qr; 26 q[++qr] = i; 27 while (ql <= qr && i - q[ql] > A) ++ql; 28 p[i] = f[q[ql]] + i * V; 29 } 30 memcpy(f, p, (k + 1) * sizeof(int)); 31 } 32 void dfsL(int x) { 33 bag(F + index(x, 0), a[x] - 1, v[x]); 34 for (int i = head[x]; i; i = nxt[i]) { 35 memcpy(F + index(i, 0), F + index(x, 0), (k + 1) * sizeof(int)); 36 dfsL(i); 37 for (int j = 1; j <= k; ++j) 38 F[index(x, j)] = max(F[index(x, j)], F[index(i, j - 1)] + v[i]); 39 } 40 } 41 void dfsR(int x, int sum) { 42 sum += v[x]; 43 for (int i = head[x]; i; i = nxt[i]) { 44 memcpy(G + index(i, 0), G + index(x, 0), (k + 1) * sizeof(int)); 45 dfsR(i, sum); 46 for (int j = 1; j <= k; ++j) 47 G[index(x, j)] = max(G[index(x, j)], G[index(i, j - 1)] + v[i]); 48 } 49 if (head[x] == 0) { 50 for (int j = 0; j <= k; ++j) 51 ans = max(ans, F[index(x, j)] + G[index(x, k - j)] + sum); 52 } 53 bag(G + index(x, 0), a[x] - 1, v[x]); 54 } 55 int main() { 56 int T = read(); 57 while (T--) { 58 n = read(); k = read(); 59 ans = 0; 60 int const rt = 1; 61 for (int i = 1; i <= n; ++i) head[i] = 0; 62 for (int i = 1; i <= n; ++i) { 63 fa[i] = read(); a[i] = read(); v[i] = read(); 64 if (fa[i] != 0) 65 add(fa[i], i); 66 } 67 memset(F + index(rt, 0), 0, (k + 1) * sizeof(int)); 68 memset(G + index(rt, 0), 0, (k + 1) * sizeof(int)); 69 dfsL(rt); 70 for (int i = 1; i <= n; ++i) head[i] = 0; 71 for (int i = n; i >= 1; --i) if (fa[i] != 0) add(fa[i], i); 72 dfsR(rt, 0); 73 printf("%d\n", ans); 74 } 75 return 0; 76 }

浙公网安备 33010602011771号