题目链接:http://poj.org/problem?id=2486
题目大意:给定一棵节点数为n的树,每个节点都放有一些苹果,现在从根节点1开始走,每走一条边算一步,每经过一个节点就能吃掉这个节点的苹果(吃掉就没了),问走m步最多能吃几个苹果?
解题思路:树形DP + 分组背包。花了一下午A掉这题,没有一点优越感,只觉得这题好猥琐。题目要求从根节点开始往下走,如果一路向下必碰南墙得不到最优解,会有回溯这个过程,因为这个过程问题变得复杂。从根节点开始往下模拟,每次选择一个分支,会有三种情况:1、人走到这个分支中去就不回来了 2、人走到这个分支中去但是还回来,人留在根中 3、人走到这个分支中区但是还回来,跑到其他分支中区。我们只有三种选择,不是be Or not to be。从根往叶子节点想,再从叶子节点往上更新答案,最后输出根结点选到的最优解就好。
模型转换:由于每次可选1..m步往下走,这个即是费用,m是容量,每个节点的苹果数为价值,每个节点为一组物品,每组物品至多选一个物品。
假设有dp[i][j][k],dp[i][j][0]表示以i为根,走了j步回到i节点能吃的的最大苹果数,dp[i][j][1]表示以i为根,走了j步不回到i节点能吃的最大苹果数,
状态转移方程比较繁琐,看代码更容易懂,代码中加注释部分即是。我自己写的代码比较挫,在看别人的解题报告时改成了这个简洁的飘逸的代码。
把题目AC了还远远不够,多看别人代码,多学习别人思想和代码中的闪光点。
测试数据:
4 3
代码:
#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,ptr,ans,vis[MAX]; int val[MAX],dp[MAX][MAX][2]; void Initial() { ptr = 1; memset(dp,0,sizeof(dp)); memset(head,0,sizeof(head)); } void AddEdge(int x,int y) { tree[ptr].v = y; tree[ptr].next = head[x],head[x] = &tree[ptr++]; } void Tree_DP(int v) { node *p = head[v]; for (int i = 0; i <= m; ++i) dp[v][i][1] = dp[v][i][0] = val[v]; while (p != NULL) { Tree_DP(p->v); for (int j = m; j >= 0; --j) //分组背包 for (int k = 0; k <= j; ++k) { //往p->v分支走一次得到结果不回到当前节点,人留在p->v分支中 dp[v][j+2][0] = max(dp[v][j+2][0],dp[v][j-k][0]+dp[p->v][k][0]); //往p->v分支走一次得到结果回到当前节点,人留在v的其他分支中 dp[v][j+1][1] = max(dp[v][j+1][1],dp[v][j-k][0]+dp[p->v][k][1]); //往p->v分支走一次得到结果又回到当前节点,人留在v节点 dp[v][j+2][1] = max(dp[v][j+2][1],dp[v][j-k][1]+dp[p->v][k][0]); } p = p->next; } } int main() { int i,j,k,a,b; while (scanf("%d%d",&n,&m) != EOF) { Initial(); for (i = 1; i <= n; ++i) scanf("%d",&val[i]); for (i = 1; i < n; ++i) { scanf("%d%d",&a,&b); if (a < b) AddEdge(a,b); else AddEdge(b,a); } Tree_DP(1); printf("%d\n",dp[1][m][1]); } }本文ZeroClock原创,但可以转载,因为我们是兄弟。