zoj 2834 蛮有意思的树形DP

这道题需要很仔细才能AC啊,本菜搞了好久才A~

题意:

你要营救的人被关在最后一个城堡 (n-1)

先给你n个数,表示征服每个城堡所需要的时间

再给你n个数,表示每个城堡(从0开始)的父亲节点是谁,题目保证 城堡 n-1 没有父节点

另外有一些规则,如果一个城堡有两个或以上的儿子被征服了,那么这个城堡的主人就会生气,他就会将这件事告诉他的父亲节点,然后层层往上一直到最后一个城堡(n-1),那么你要救的人就会被杀掉,所以你在杀掉某个节点 u 的两个儿子的时候就必须跑到u城堡去杀u,当你征服最后一个城堡(n-1)的时候游戏就结束了

问:这个游戏最多能玩多少时间。

已经有人写了一个很详细的解题报告,所以我就懒的写了

注意一点:如果给你一个森林,怎么办

http://hi.baidu.com/sheep_finalfreedom/blog/item/7879bb2b995c04f598250a7a.html

首先我们考虑king koopa,当他被打败的时候,游戏必须结束,所以他最多有两个儿子被打败,而且对于第二个被打败的儿子,也是同样的情况,即king koopa的【第二个被打败的儿子】最多有两个儿子被打败,而king koopa的第一个被打败的儿子,以它(这个它指的是king koopa的第一个被打败的儿子)为根的整棵子树都能被打掉,比如它有三个儿子,那么其中2只被打败之后,我们必须马上去打他们的father,但是father打完之后我们还可以倒回去把剩下的那个儿子给收拾掉!
上面已经考虑到了DP的两个方面,我们用sum【i】来表示【完全打败】以i为根的子树所用的时间,dp【i】
表示以i为根的时候,i最多只能有两个儿子被打败时所用的时间(因为它的其他儿子已经来不及打了,king koopa的倒下会直接结束游戏!)
但是还有一个地方需要考虑进去,假设king koopa有4个儿子,我们只能打掉其中2只,另外2只不能动,但是并不代表这两只的儿子也不能打,相反,作为贪心的超级玛丽,我们必须去打这两只的后代!
所以用more【i】来表示以i为根的子树,i不被打败时的最大游戏时间
more【i】=max(sum【child1】+more【otherchildren】)枚举这个child1
则dp【i】=max(sum【child1】+dp【child2】+more【otherchildren】)枚举child1和child2



下面是我的代码

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
#define CLR(v) memset(v,0,sizeof(v));
const int maxn = 1010;
int val[maxn];
vector<int> edge[maxn];
int  more[maxn],dp[maxn],sum[maxn];
void get_sum(int u,int f)
{
	sum[u]=val[u];
	int sz=edge[u].size();
	for(int i=0;i<sz;i++)
	{
		int v=edge[u][i];
		if(v==f) continue; 
		get_sum(v,u);
		sum[u]+=sum[v];
	}
}
void get_more(int u,int f)
{
	more[u]=0;
	int sz=edge[u].size();
	int s=0;
	for(int i=0;i<sz;i++)
	{
		int v=edge[u][i];
		if(v==f) continue;
		get_more(v,u);
	}
	for(int i=0;i<sz;i++) s+=more[edge[u][i]];
	for(int i=0;i<sz;i++)
	{
		int v=edge[u][i];
		more[u]=max(more[u],sum[v]+s-more[v]);
	}
}
bool vis[maxn];
void dfs(int u,int f)
{
	dp[u]=val[u];
	int sz=edge[u].size();
	if(sz==1)	dp[u]=max(dp[u],sum[edge[u][0]]+val[u]);
	
	int s=0;
	for(int i=0;i<sz;i++)
	{
		int v=edge[u][i];
		if(v==f) continue;
		dfs(v,u);
	}
	for(int i=0;i<sz;i++)	s+=more[edge[u][i]];
	for(int i=0;i<sz;i++)
	{
		for(int j=i+1;j<sz;j++)
		{
			int son1=edge[u][i];
			int son2=edge[u][j];
			dp[u]=max(dp[u],dp[son1]+sum[son2]+s-more[son1]-more[son2]+val[u]);
			dp[u]=max(dp[u],dp[son2]+sum[son1]+s-more[son1]-more[son2]+val[u]);
		}
	}
}
bool root[maxn];
int main()
{
	int n;
	while(scanf("%d",&n),n)
	{
		for(int i=0;i<n;i++) scanf("%d",&val[i]),edge[i].clear();
		memset(root,false,sizeof(root));
		for(int i=0,j;i<n;i++)
		{
			scanf("%d",&j);
		    if(j==-1) root[i]=true;
			if(j!=-1) edge[j].push_back(i);
		}
		CLR(dp);CLR(more);CLR(sum);
		int ans=0;
		for(int i=0;i<n-1;i++)
		{
			if(root[i])
			{
				get_sum(i,-1);
				ans+=sum[i];
			}
		}
		get_sum(n-1,-1);
		get_more(n-1,-1);
		dfs(n-1,-1);
		printf("%d\n",ans+dp[n-1]);
	}
	return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值