POJ 2486 apple tree
题意:
一棵树, 树上的节点都有 value, 节点之间的 cost 为1, 从树根开始走, 给定最大 cost K, 求解所能得到的最大 value
思路:
1. 树形 DP
2. 我起初认为这是 hdoj 鬼吹灯 的简化版本, 后来才发现鬼吹灯是 apple tree 的简化版本. 具体来说, 鬼吹灯中一个根节点的孩子节点没有依赖关系, 可以以任意的顺序遍历孩子节点, 状态转移方程可以设计的比较简单, 而这道题则不然, 至于原因, 可以看 (3)
3.
8 4 2 1 4 3 3 2 4 1 1 2 1 3 1 4 2 5 2 6 3 7 3 8
在 poj 的 discuss 上找到一个上面那个案例, 才意识到状态转移方程的不足. 根节点 1 有 2,3,4 共3个孩子, 正确的遍历顺序应该是 1->4->1->3->7, 结果是13
而我给出的路径是 1->2->1->3->7. 上面转移方程的不足在于对孩子节点的遍历时严格按照测试用例给定的孩子排列顺序进行的, 对孩子节点 a, b 进行遍历, 则 a 必然是返回根节点, b 不一定需要返回. 但是 a 必然小于 b(没这个要求)
4. 正解思路.
dp[1][u][i] 表示以 u 为根的树最大花费为 i, 最后走回 u 能够得到的最大收益
dp[0][u][i] 表示以 u 为根的树最大花费为 i, 最后没有走回 u 得到的最大收益
状态转移方程
dp[1][u][i+2] = max(dp[1][u][i+2], dp[1][u][j] + dp[1][v][i-j]); // 从 u 出发最后又回到 u, 这便是鬼吹灯的状态转移方程 dp[0][u][i+2] = max(dp[0][u][i+2], dp[1][v][j] + dp[0][u][i-j]); // 从 u 出发最后没能回到 u, 并且停在了节点 v 的哥哥节点 dp[0][u][i+1] = max(dp[0][u][i+1], dp[0][v][j] + dp[1][u][i-j]); // 从 u 出发但没回到 u, 最终停在节点 v
最后一行解释了如何解除孩子节点间先后顺序这个依赖, 很是巧妙
用一种很优雅的方式解决了全排列问题
总结
1. 以前做树形DP, dp[i][j] 一般表示在以 i 为节点的根上恰好走 j 步, 而此题却是最多走 j 步
2. 上面的状态转移方程中, j+2, j +1, 以及三个状态转移方程的顺序是精心设计过的. j+2, j +1 这种设置有普遍性, 以后要考虑使用. 我第一次写的代码写成这样
// 求解 dp2, 最终停在第 v 个分支上 for(int k = K; k >= 0; k --) { for(int s = 1; s <= k; s ++) { if(dp1[u][k-s] != -INF && dp2[v][s-1]!= -INF) { dp2[u][k] = max(dp2[u][k], dp1[u][k-s] + dp2[v][s-1]); } } } // 走向 第 v 个分支并走回来 for(int k = K; k >= 0; k --) { for(int s = 0; s <= k-2; s++) { if(dp1[u][k-s-2] != -INF && dp1[v][s] != -INF) dp1[u][k] = max(dp1[u][k], dp1[u][k-s-2]+dp1[v][s]); } }
因为没使用 j+2, j+1 这种技巧, 使得 dp1, dp2 必须分开写
代码:
抄了一份代码, 卡在这道题上太久了
#include <iostream> #include <algorithm> using namespace std; const int MAXN = 110; const int MAXD = 210; struct edge { int v; edge *next; } *V[MAXN], ES[MAXN*2]; int EC, N, K, val[MAXN], dp[2][MAXN][MAXD]; bool vis[MAXN]; void addedge(int u, int v) { ES[++EC].next = V[u]; V[u] = ES + EC; V[u]->v = v; } void initdata(int n) { EC = 0; memset(V, 0, sizeof(V)); memset(vis, false, sizeof(vis)); for(int i = 1; i <= n; i ++) { scanf("%d", &val[i]); } for(int i = 1; i <= n-1; i ++) { int a, b; scanf("%d %d", &a, &b); addedge(a, b); addedge(b, a); } } void treedp(int u, int vol) { vis[u] = true; for(int j = 0; j <= vol; j++) { dp[0][u][j] = dp[1][u][j] = val[u]; } for(edge *e = V[u]; e; e = e->next) { int v = e->v; if(vis[v]) continue; treedp(v, vol-1); for(int i = vol; i >= 0; i--) { for(int j = 0; j <= i; j++) { dp[0][u][i+2] = max(dp[0][u][i+2], dp[1][v][j] + dp[0][u][i-j]); dp[1][u][i+2] = max(dp[1][u][i+2], dp[1][u][j] + dp[1][v][i-j]); dp[0][u][i+1] = max(dp[0][u][i+1], dp[0][v][j] + dp[1][u][i-j]); } } } } int main() { //freopen("E:\\Copy\\ACM\\poj\\2486\\in.txt", "r", stdin); while(cin >> N >> K) { initdata(N); treedp(1, K); cout << dp[0][1][K] << endl; } return 0; }