Loading

[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 }

 

posted @ 2020-11-25 16:16  王鲲鹏  阅读(84)  评论(0)    收藏  举报