poj2486解题报告

      树形DP.
与以往一些分配资源的树形动归相比,这道题的最大不同就是可以往回走。。。即可以从子节点回到根节点。
建树问题:直接dfs在多叉树上做就可以。
设back[x][j]为从标号为x的根节点向下走j步,最终回到i所取得的最大价值;
设pass[x][j]为从标号为x的根节点向下走j步,最终不一定回到i所取得的最大价值。
这里(0<=j<=Max_step)    
初始化:
for(i = 0 ; i <= Max_step ; ++i)
{
            back[x][i] = pass[x][i] = val[x];
}
现在我们考虑为x的子节点son分配资源。下面设从节点son出发向下走的步数为k.(也即给节点son分配的资源)
换一种思考的方式,把son看作一部分,把根节点i剩余的子节点及以其为根的子树看作另一部分。
首先看back[x][j].
我们想要回到根节点i,把这个过程分为两个部分:
step1:从根节点x到达son,从son遍历son的子树再回到son,从son回到son的根节点x;
step2:用剩余的步数遍历根节点x其余的子树,再回到根节点x.
获得的价值:back[son][k] + back[x][j - k - 2]
为什么是j - k - 2呢,因为一共步数为j,从son往返消耗了步数2,在son处消耗了步数k,故还剩(j - 2 - k).
我们得到: back[x][j] = max(back[x][j] , back[son][k] + back[x][j - 2 - k])   (0<=k<=j-2)
接下来看pass[x][j].
既然不一定回到根节点x,那么最后的位置会是哪里呢?
我们刚才说过,要把以当前决策的子节点son为根节点的子树和根节点x的其余子树分开来考虑。
case1:
最后的位置在以子节点son为根节点的树上。
那么:
step1:从根节点遍历其余的子树,最终回到根节点;
step2:从根节点遍历以son为根节点的树,最终不一定回到son.
我们得到:  pass[x][j] = max(pass[x][j] , pass[son][k] + back[x][j - 1 - k])  (0<=k<=j-1)
多出的那个“1”和上面类似,是从根节点x到子节点son的一步。
case2:
最后的位置在其余的子树上。
step1:从根节点遍历以son为根节点的子树,最终回到son,并回到根节点x.
strep2:从根节点出发遍历其余的子树,最终不一定回到son,不知所踪。
我们得到 : pass[x][j] = max(pass[x][j] , back[son][k] + pass[x][j - 2 - k]) (0<=k<=j-2)
状态转移方程大功告成。
还有一个重要问题:j的枚举顺序问题。用类似滚动数组一类东西从Max_size-0枚举。

代码就比较水逼了。

#include<cstdio>
#include<cstring>
inline int max(int a , int b)
{
	return a > b ? a : b;
}
int pass[101][201] , back[101][201];
int val[101];
int n,m;
int head[101] , next[202] , end[202] , ind;
void addedge(int a , int b)
{
	int q = ++ind;
	end[q] = b;
	next[q] = head[a];
	head[a] = q;
}
bool ext[101];
int n_son[101] , son[101][101];
void build(int x)
{
	ext[x] = 1;
	for(int i = head[x] ; i ; i = next[i])
	{
		if(!ext[end[i]])
		{
			son[x][++n_son[x]] = end[i];
			build(end[i]);
		}
	}
}
void dfs(int x)
{
	int i,j,k;
	for(i = 0 ; i <= m ; ++i)
	{
		pass[x][i] = back[x][i] = val[x];
	}
	for(i = 1 ; i <= n_son[x] ; ++i)
	{
		dfs(son[x][i]);
		for(j = m ; j >= 0 ; --j)
		{
			for(k = 0 ; k + 2 <= j ; ++k)
			{
				back[x][j] = max(back[x][j] , back[son[x][i]][k] + back[x][j - 2 - k]);
			}
			for(k = 0 ; k + 2 <= j ; ++k)
			{
				pass[x][j] = max(pass[x][j] , back[son[x][i]][k] + pass[x][j - 2 - k]);
			}
			for(k = 0 ; k + 1 <= j ; ++k)
			{
				pass[x][j] = max(pass[x][j] , pass[son[x][i]][k] + back[x][j - 1 - k]);
			}
		}
	}
}
int main()
{
	register int i;
	while(scanf("%d%d",&n,&m) != EOF)
	{
		memset(pass , 0 , sizeof(pass));
		memset(back , 0 , sizeof(back));
		for(i = 1 ; i <= n ; ++i) scanf("%d",&val[i]);
		int a,b;
		ind = 0;
		memset(head , 0 , sizeof(head));
		memset(end , 0 , sizeof(end));
		memset(next , 0 , sizeof(next));
		for(i = 1 ; i < n ; ++i)
		{
			scanf("%d%d",&a,&b);
			addedge(a , b);
			addedge(b , a);
		}
		memset(ext , 0 , sizeof(ext));
		memset(n_son , 0 , sizeof(n_son));
		build(1);
		dfs(1);
		printf("%d\n",max(pass[1][m] , back[1][m]));
	}
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值