【题目来源】http://acm.hdu.edu.cn/showproblem.php?pid=1561
【题目解析】有树形依赖的背包问题。
流传较广的是多叉树转二叉树的DP方法。首先运用“左儿子右兄弟”的方法将多叉树转化为二叉树。这样的话,DP的进程就更加容易。由于是二叉,那么只需要枚举分配给左儿子多少个点,分配给右儿子(右兄弟)多少个点即可。不过需要注意的是枚举给左儿子的时候记得给父亲留一个点,因为是依赖的;枚举给右儿子的时候可以全部给它,因为它和父节点是兄弟关系。
这里要解析的是直接多叉树DP,即直接在原有的树上面进行DP。从递归的角度来考虑:把根节点看成是一个容量为V背包,其所有的子树看成是物品。枚举每个子树,对于某个子树来说,先去掉这棵子树根节点的体积W,递归求出剩下的V-W容量的背包能装下这个子树的子树的物品。那么将会得到一个从0..V-W的一个01背包,把每一个体积及其对应的价值看成是一个物品,因此共有V-W+1个物品,每个物品再算上所去掉的根节点。那么新的这V-W+1个物品可以看成是一个分组,物品之间是互斥的,只能选择其一。由此可见,对于每个根节点来说,它的所有子树就可以看成是一组组的分组物品,各组内部的物品之间是互斥的,只能选择其一。所以问题最终的本质是在树上进行的分组背包。
【代码如下】
1 #include <iostream> 2 #include <cstring> 3 4 using namespace std; 5 6 const int Max = 201; 7 8 struct edge 9 { 10 int v; 11 edge* next; 12 edge(int _v, edge* _next) : v(_v), next(_next) {} 13 }* E[Max]; 14 15 int N, V, F[Max][Max], C[Max]; 16 17 void Clear() 18 { 19 memset(F, 0, sizeof(F)); 20 memset(E, 0, sizeof(E)); 21 } 22 23 void Dp(int i, int V) 24 { 25 for (edge* j = E[i]; j; j = j -> next) 26 { 27 int v = j -> v; 28 Dp(v, V - 1); 29 for (int k = V; k >= 0; k --) 30 for (int l = 0; l < min(V, k); l ++) 31 F[i][k] = max(F[i][k], F[i][k - (l + 1)] + F[v][l] + C[v]); 32 } 33 } 34 35 inline void edgeAdd(int x, int y) 36 { 37 E[x] = new edge(y, E[x]); 38 } 39 40 void Init() 41 { 42 for (int i = 1, x; i <= N; i ++) 43 { 44 cin >> x >> C[i]; 45 edgeAdd(x, i); 46 } 47 } 48 49 int main() 50 { 51 cin >> N >> V; 52 while (N || V) 53 { 54 Init(); 55 Dp(0, V); 56 cout << F[0][V] << endl; 57 Clear(); 58 cin >> N >> V; 59 } 60 return 0; 61 }