《GMOJ-Senior-4814 tree》题解

题目大意

给出一棵带点权的树,要选中若干个结点,把被选中的结点到根结点( 1 1 1号结点)路径上所有结点(包括被选中的结点和根结点)打上标记,给出被打上标记的结点的数量上限,求最大化的被打上标记的结点的权值和。
对于前 20 % 20\% 20%的数据, 1 ≤ 树的结点个数 , 被打上标记的结点的数量上限 ≤ 10 1 \leq \text{树的结点个数} , \text{被打上标记的结点的数量上限} \leq 10 1树的结点个数,被打上标记的结点的数量上限10
对于前 60 % 60\% 60%的数据, 1 ≤ 树的结点个数 , 被打上标记的结点的数量上限 ≤ 100 1 \leq \text{树的结点个数} , \text{被打上标记的结点的数量上限} \leq 100 1树的结点个数,被打上标记的结点的数量上限100
对于 100 % 100\% 100%的数据, 1 ≤ 树的结点个数 , 被打上标记的结点的数量上限 ≤ 3000 1 \leq \text{树的结点个数} , \text{被打上标记的结点的数量上限} \leq 3000 1树的结点个数,被打上标记的结点的数量上限3000 ∣ 点的权值 ∣ ≤ 1 0 5 | \text{点的权值} | \leq 10^5 点的权值105。数据有梯度,保证给出的是合法的树。

分析

这是一道树形动态规划(树形 D P DP DP)题。
我们定义 f i , j f_{i,j} fi,j 1 ≤ i ≤ 树的结点个数 1 \leq i \leq \text{树的结点个数} 1i树的结点个数, 1 ≤ j ≤ 以 i 为根的子树的大小 1 \leq j \leq \text{以} i \text{为根的子树的大小} 1ji为根的子树的大小)为以结点 i i i为根的子树中,被打上标记的结点的数量上限为 j j j时求最大化的被打上标记的结点的权值和。尽管题目让我们求的是到根结点的路径,要用的是结点的父亲,但只要我们满足 j ≥ 1 j \geq 1 j1,就能保证被选中的结点到根结点上所有结点(包括被选中的结点和根结点)都被算到。
显然 f x , 1 = 点 x 的权值 f_{x,1}= \text{点} x \text{的权值} fx,1=x的权值,那么怎么求出 f x , i f_{x,i} fx,i 2 ≤ i ≤ 以 x 为根的子树的大小 2 \leq i \leq \text{以} x \text{为根的子树的大小} 2ix为根的子树的大小)呢?我们可以用一种类似背包问题的解法的方法。将 f x , i f_{x,i} fx,i i = 2 , 3 , ⋯   , 以 x 为根的子树的大小 i=2,3, \cdots , \text{以} x \text{为根的子树的大小} i=2,3,,x为根的子树的大小)设为 − ∞ - \infty ,并设 s = 1 s=1 s=1,然后依次枚举结点 x x x的儿子。设结点 y y y为当前枚举到的儿子,我们将 f x , j + k f_{x,j+k} fx,j+k更新为 max ⁡ { f x , j + k , f x , j + f y , k } \max \{ f_{x,j+k},f_{x,j}+f_{y,k} \} max{fx,j+k,fx,j+fy,k}(其中 j j j s s s向下枚举到 1 1 1 k k k从以 y y y为根的子树的大小向下枚举到 1 1 1),并把 s s s加上以 y y y为根的子树的大小。有了 f f f后,我们可以很快求出答案了:答案为 max ⁡ { f 1 , i ∣ 1 ≤ i ≤ 被打上标记的结点的数量上限 } \max \{ f_{1,i}|1 \leq i \leq \text{被打上标记的结点的数量上限} \} max{f1,i1i被打上标记的结点的数量上限}
根据我们的思路,可以写出以下代码:

#include<cstdio>
int max(int x,int y)//最大值函数
{
	if(x>y)
	{
		return x;
	}
	return y;
}
int len;struct edge{int to/*终点*/,next/*下一条边*/;}e[6006];//边
struct node{int v,last/*连出的最后一条边*/,f[3003],s/*s和子树大小*/;}a[3003];//点
void link(int x,int y)//连边函数
{
	++len;
	e[len].to=y;
	e[len].next=a[x].last;
	a[x].last=len;
	return;
}
int n;
void dfs(int x/*当前结点*/,int fa/*当前结点的父亲*/)
{
	a[x].s=1;//初始化s
	a[x].f[1]=a[x].v;//初始化f
	for(int i=2;i<=n;i++)
	{
		a[x].f[i]=-2147483647;
	}
	for(int i=a[x].last;i!=-1;i=e[i].next)/遍历每一条出边
	{
		int y=e[i].to;
		if(y!=fa)//如果y不是x的父亲(即y是x的儿子)时
		{
			dfs(y,x);//递归
			for(int j=a[x].s;j>=1;j--)//更新f
			{
				for(int k=a[y].s;k>=1;k--)
				{
					a[x].f[j+k]=max(a[x].f[j+k],a[x].f[j]+a[y].f[k]);
				}
			}
			a[x].s+=a[y].s;//更新s
		}
	}
	return;
}
int main()
{
	freopen("tree.in","r",stdin);//注意要开文件输入输出
	freopen("tree.out","w",stdout);
	int lim;
	scanf("%d%d",&n,&lim);//读入n,lim
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i].v);//读入v[i]
		a[i].last=-1;//初始化a
	}
	len=0;
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);//读入边
		link(x,y);//连边(注意树是无向图,要连双向边)
		link(y,x);
	}
	dfs(1,-1);//求出f
	int ans=-2147483647;//求出答案
	for(int i=1;i<=lim;i++)
	{
		ans=max(ans,a[1].f[i]);
	}
	printf("%d",ans);//输出答案
	return 0;
}

总结

做树形 D P DP DP的题目时,一般要用到结点之间的父子关系,我们要先找出树上的父子关系,然后根据推出来的父子间数量的关系得出答案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值