题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1561
题目大意:给定n个地点,每个地点藏有cost[i]的宝物,取得某些宝物有时需要先取其他宝物,现在让我们选m个地点问最多可以选多少宝物?
解题思路:和Poj1155、1947一样,都是在树形结构上进行分组背包处理。本题的依赖关系可以理解成树边,到达子节点必须先经过父节点,本质是一样的,这样可以建立起一个森林,如果为每棵树的添加一个公共父节点0那就成了一棵树了,这是个实用性很强的小技巧。建立起一棵树就开始如何进行dp,如何从子节点获取信息。
一个子节点就可以返回m个状态,每个状态表示容量为j(j<=m)时选最多的宝物,而一个子节点中只可以选择一个状态进行转移,每个节点有若干个子节点,问题就转换为分组背包,几个子节点就是几个分组背包,体积是选几个地点,价值是宝物价值。
状态转移方程: dp[v][1] = Money[v]; (v为叶子节点)
dp[v][j] = max(dp[v][j],dp[v][j-i] + dp[k][i] );(v为非叶子节点,j表示用户个数,i为容量,k为v的子节点,)
算法复杂度O(sum(num[i],num[s])) (num[i]为某个节点的叶子子孙个数,num[s]为i的子节点的叶子子孙个数)
本题加了个剪枝,每次转移时不必都从最大容量m开始,而可以把当前可能转移的最大容量算出来进行转移,最大容量就是子节点个数,速度快了10几倍。
测试数据:
3 2
代码:
#include <stdio.h> #include <string.h> #define MAX 210 #define max(a,b) (a)>(b)?(a):(b) struct node { int v; node *next; }*head[MAX],tree[MAX]; int n,m,money[MAX]; //root表示输入时是否与根相连 int ptr,dp[MAX][MAX]; void Initial() { ptr = 1; memset(dp,0,sizeof(dp)); memset(head,NULL,sizeof(head)); } void AddEdge(int fa,int son) { tree[ptr].v = son; tree[ptr].next = head[fa]; head[fa] = &tree[ptr++]; } int Tree_DP(int v) { int i,j,k,tot = 1; node *p = head[v]; while (p != NULL) { tot += Tree_DP(p->v); int tp = tot < m ? tot : m; //加个剪枝,这个到目前为止能到达最大容量 for (j = tp; j >= 1; --j) //分组背包 for (i = 1; i <= j; ++i) dp[v][j] = max(dp[v][j],dp[p->v][i]+dp[v][j-i]); p = p->next; } if (v != 0) { for (j = m; j >= 1; --j) dp[v][j] = dp[v][j-1] + money[v]; //必选当前节点自己 } return tot; } int main() { int i,j,k,a,b; while (scanf("%d%d",&n,&m),n+m) { Initial(); for (i = 1; i <= n; ++i) { scanf("%d%d",&a,&b); money[i] = b; AddEdge(a,i); } Tree_DP(0); printf("%d\n",dp[0][m]); } }
本文ZeroClock原创,但可以转载,因为我们是兄弟。