【树形DP】【分组背包】【HDU1561】

【题目来源】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 }
原文地址:https://www.cnblogs.com/GXZC/p/2851761.html