HDU1520 Anniversary party(树形DP)

本文介绍了一道关于树形动态规划的经典题目——求解员工派对的最大欢乐值之和问题。通过两种不同的实现思路对比,展示了如何避免在树形结构中重复计算的问题,并给出了正确的递归解决方案。

Anniversary party

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 16631    Accepted Submission(s): 6326


 

Problem Description

There is going to be a party to celebrate the 80-th Anniversary of the Ural State University. The University has a hierarchical structure of employees. It means that the supervisor relation forms a tree rooted at the rector V. E. Tretyakov. In order to make the party funny for every one, the rector does not want both an employee and his or her immediate supervisor to be present. The personnel office has evaluated conviviality of each employee, so everyone has some number (rating) attached to him or her. Your task is to make a list of guests with the maximal possible sum of guests' conviviality ratings.

 

 

Input

Employees are numbered from 1 to N. A first line of input contains a number N. 1 <= N <= 6 000. Each of the subsequent N lines contains the conviviality rating of the corresponding employee. Conviviality rating is an integer number in a range from -128 to 127. After that go T lines that describe a supervisor relation tree. Each line of the tree specification has the form: 
L K 
It means that the K-th employee is an immediate supervisor of the L-th employee. Input is ended with the line 
0 0

 

 

Output

Output should contain the maximal sum of guests' ratings.

 

 

Sample Input

 

7 1 1 1 1 1 1 1 1 3 2 3 6 4 7 4 4 5 3 5 0 0

 

 

Sample Output

 

5

 

 

Source

Ural State University Internal Contest October'2000 Students Session

 

 

Recommend

linle

 很基础的树形DP入门题啦。题目大意就是每个人都有一个欢乐值,在有直属关系的同事不能选的前提下,请问能达到的最大欢乐值之和。也就是所谓的求最大独立集。

作为刚接触树形DP的萌新,第一想法是:从叶子节点向上,通过这个两个转移方程

dp[fa][0]+=max(dp[x][0],dp[x][1]);//不取自身

dp[fa][1]+=dp[x][0];//取自身

来得到最大的独立集。用一个队列加上一个set来维护节点,perfect!

代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<set>
using namespace std;
const int size=6005;
int edge[size];
int dp[size][2];
int unleaf[size];
int v[size];
queue<int> point;
set<int> s;
int main()
{
	int n;
	while(cin>>n)
	{
		while(!point.empty()) point.pop();
		s.clear();
		memset(dp,0,sizeof(dp));
		memset(unleaf,0,sizeof(unleaf));
		memset(edge,0,sizeof(edge));
		int i;
		for(i=1;i<=n;i++)
		{
			scanf("%d",&v[i]);//读入每个人的快乐值 
		}
		int a,b;
		
		while(~scanf("%d%d",&a,&b)&&a)
		{
			edge[a]=b;//a的父节点是b 
			unleaf[b]=1;//假如他有过作父节点的经历,则一定不是叶子节点 
		}
		for(i=1;i<=n;i++)
		{
			if(!unleaf[i])
			{
				point.push(i);//把所有的叶子节点入队 
			}
			dp[i][1]=v[i];
			dp[i][0]=0;
		}
		int maxn=0;
		//从叶子节点向根节点dp 
		while(!point.empty())
		{
			while(!point.empty())
			{
				int x=point.front();
				point.pop();
				int fa=edge[x];
				dp[fa][0]+=max(dp[x][0],dp[x][1]);//不取自身 
				dp[fa][1]+=dp[x][0];//取自身 
				if(fa) s.insert(fa);//去重 
			}
			for(auto It=s.begin();It!=s.end();It++)
			{
				int temp=*It;
				maxn=max(dp[temp][0],maxn);
				maxn=max(dp[temp][1],maxn);
				point.push(temp);
			}
			s.clear();
		} 
		cout<<maxn<<endl;
	}
	return 0;
}

编译,样例通过,提交。

WA了。。。。。。

到底是怎么回事呢?我们来看下面这幅图:

不难发现,按照我的一层一层向上更新的办法更新到根节点时,x节点尚未更新,因此左节点的内容就没有加上来,自然就错了。可以看出我一开始的想法只适用于完美叉树。

因此我们需要修改自己的想法,从根节点向下进行搜索,再利用递归的原理求出子节点的值。正确代码如下:

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int size=6005;
int edge[size];
int dp[size][2];
int unroot[size];
int v[size];
vector<int> son[size];
int flag[size];
void dfs(int node)
{
	flag[node]=1;
	for(int i=0;i<son[node].size();i++)
	{
		dfs(son[node][i]);
		int s=son[node][i];
		dp[node][0]+=max(dp[s][0],dp[s][1]);
		dp[node][1]+=dp[s][0];
	} 
}
int main()
{
	int n;
	while(cin>>n)
	{
		memset(dp,0,sizeof(dp));
		memset(unroot,0,sizeof(unroot));
		memset(edge,0,sizeof(edge));
		memset(flag,0,sizeof(flag));
		int i;
		for(i=1;i<=n;i++)
		{
			scanf("%d",&v[i]);//读入每个人的快乐值 
			son[i].clear();
		}
		int a,b;
		
		while(~scanf("%d%d",&a,&b)&&a)
		{
			son[b].push_back(a);//a的父节点是b 
			unroot[a]=1;//假如他有过作子节点的经历,则一定不是根节点 
		}
		int root;
		for(i=1;i<=n;i++)
		{
			if(!unroot[i])
			{
				root=i;
			}
			dp[i][1]=v[i];
			dp[i][0]=0;
		}
		dfs(root);
		int maxn=max(dp[root][0],dp[root][1]);
		cout<<maxn<<endl;
	}
	return 0;
}

 

转载于:https://www.cnblogs.com/fly-white/p/10092771.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值