题目链接: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
1 2 3 4
1 2
2 3
2 4
2 1
0 11
1 2
3 2
0 1 2
1 2
1 3
3 0
1 2 3
1 2
1 3
3 3
1 2 3
1 2
1 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原创,但可以转载,因为我们是兄弟。