【参考资料】徐持横--2009年国家集训队论文《浅析几类背包问题》
http://wenku.baidu.com/view/8ab3daef5ef7ba0d4a733b25.html
《背包九讲》
http://wenku.baidu.com/view/8b3c0d778e9951e79b892755.html
【问题模型】http://www.rqnoj.cn/Problem_30.html
有树形依赖的背包问题。
(引自以上参考资料内容)
【泛化物品定义】考虑这样的物品,它没有固定的体积和价值,它的价值随着分配给它的体积的变化而变化。抽象成数学模型是一个定义域为[0,V]中整数的函数H[V],对于每个在定义域中的v,对应一个价值H[v]。
【泛化物品的和】将2个泛化物品合并成为1个泛化物品,方法是枚举分配的体积:
H[v]=Max{H1[i] + H2[v - i]} 0<=i<=v;
求出从0..V上所有的H[v],即得到这2个泛化物品的和。时间复杂度为O(V^2)
【算法】在上一篇blog中,我提到了2种方法。其一是将多叉树转化为二叉树(左儿子右兄弟转化法),然后枚举分配给左儿子和右兄弟的容量,再递归进行处理。其二是直接进行多叉树DP。二者的本质是一样的,每个节点看作一个泛化物品,问题的目标就是求出根节点这件泛化物品与其所有子树的泛化物品的和。
每次合并2个泛化物品,效率为O(V^2),一棵树有N个节点,一共需要合并N-1次,因此算法的总效率为O(NV^2)
【优化算法】徐持横的算法级别仅为O(NV),下面写出本人对该方法的理解:
首先最重要的一点是,这个方法中,F[I][V]表示的内容不是从以I为根的子树中,容量为V的背包选取物品所能得到的最大价值。切记这一点!传统在考虑树形的问题时候,往往会下意识地认为父亲节点是由子节点更新而来。
事实上,F[I][V]表示的是从根节点到I这条路径及其左边的所有节点(不包含总的根节点),以及以I为根的子树的所有节点中,容量为V的背包选取物品所能得到的最大价值。
对于以I为根的子树,当前要处理I的一个儿子v,不妨假设一定选择了v,这样就可以放心的选择v的子节点(这点很重要)。
【泛化物品与普通物品的和】H[v]=H1[v] 0<=v
H[v]=Max{H[v],H[v-w]+c} w<=v<=V;
因此,求泛化物品与普通物品的和的效率为O(V);
所以,用上面求泛化物品与普通的和的方法,F[v][k]=F[I][k]+C[v](0<=k<=V-w,w表示物品v的体积,C[v]表示它的价值,这个地方要注意一件事情:其实按理来说应该是F[v][k+w]=F[I][k]+C[v],后者理解起来更直观,不过只是为了处理方便一点,才写成前者。),这意味着,此时v得到了从根节点到I这条路径及其左边的所有节点的信息,并在此基础上,强制放入了物品v,以这个过程进行递归重复。当递归到底层开始回退的时候,子节点存放的信息和父节点存放的信息,就变成了有交集的2个泛化物品。也就是说,当回退到v和I的时候,F[v]和F[I]的关系是,F[v]是F[I]在一定选择了物品v的情况下的拓展,因为物品v是可以不选的,因此,要求出F[v]和F[I]的并。
【泛化物品的并】因为两个泛化物品存在交集,不能够同时都取,因此需要求两者的并。
H[v] = Max{H1[v], H2[v]} 0<=v<=V;
因此,求泛化物品的并的效率是O(V);
【算法总效率】一棵树有N个节点,每个节点被访问一次,每次需要进行的操作是求泛化物品与普通的和(O(V)),以及求泛化物品的并(O(V)),因此总效率为O(NV)。
【代码】http://www.rqnoj.cn/Problem_30.html
1 #include2 3 using namespace std; 4 5 struct edge 6 { 7 int v; 8 edge* next; 9 edge(int _v, edge* _next) : v(_v), next(_next) {} 10 }* E[1001]; 11 int N, V, C[1001], F[1001][101]; 12 13 void Dp(int i, int iV) 14 { 15 if (iV <= 0) return ; 16 for (edge* j = E[i]; j; j = j -> next) 17 { 18 int v = j -> v; 19 for (int k = 0; k < V; k ++) F[v][k] = F[i][k] + C[v]; 20 Dp(v, V - 1); 21 for (int k = 1; k <= V; k ++) F[i][k] = max(F[i][k], F[v][k - 1]); 22 } 23 } 24 25 inline void edgeAdd(int x, int y) 26 { 27 E[x] = new edge(y, E[x]); 28 } 29 30 int main() 31 { 32 cin >> N >> V; 33 for (int i = 1; i <= N; i ++) cin >> C[i]; 34 int x, y; 35 while (cin >> x >> y) edgeAdd(x, y); 36 Dp(0, V); 37 cout << F[0][V] << endl; 38 return 0; 39 }