HDU6540 Neko and tree

这是一道HDU6540的题目,要求在一颗树中选择关键节点,使得任意两个选中节点之间的距离不超过k,求解方案数。通过树形DP和泛化背包思路解决,利用前缀和优化复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://acm.hdu.edu.cn/showproblem.php?pid=6540

Problem Description
Neko has a tree with n nodes.
There are m key nodes on tree. Neko want to you to selecte some key nodes satisfying the distance between any two selected nodes less than or equal to k.
Neko thinks this work is too easy,so Neko want to know how many different way for selecting nodes.
Calculate the answer after mod 109+7.
Note that you have to select at least one key node.

题意:一颗树,n个结点,其中m个关键结点,要求选 只 包 含 关 键 结 点 的 结 点 集 , 其 中 任 意 两 点 间 距 离 都 不 超 过 k 只包含关键结点的结点集,其中任意两点间距离都不超过k k,求选的方案数。
思路:树形dp,设 f ( u , i ) : 以 u 为 根 的 子 树 , 在 满 足 任 意 两 点 间 距 离 不 超 过 k 的 前 提 下 , 结 点 集 中 距 离 u 最 远 的 点 距 离 u 为 i 的 方 案 数 f(u,i):以u为根的子树,在满足任意两点间距离不超过k的前提下,结点集中距离u最远的点距离u为i的方案数 f(u,i):ukuui
用泛化背包的思想更新。
需要用前缀和优化复杂度。读了这篇https://blog.youkuaiyun.com/cat_pikapikapi/article/details/90521780

#include<bits/stdc++.h>
using namespace std;
#define maxn 5010
#define mod 1000000007
#define ll long long
#define M(x) ((x)%=mod)

int n,m,k;
ll f[maxn][maxn],pre[maxn][maxn],g[maxn],is[maxn];
ll ans;
vector<int> G[maxn];

void cal(int u,int v) //将子树v加入u的更新操作
{										
	for(int i=1;i<k;i++)g[i]=f[u][i]*pre[v][min(i,k-i)-1],M(g[i]);
	for(int i=1;i<k;i++)g[i]+=pre[u][min(i,k-i)]*f[v][i-1],M(g[i]);
	for(int i=1;i<=k/2;i++)g[i]-=f[u][i]*f[v][i-1],g[i]=(g[i]+mod)%mod;//最深在u+最深在v-uv同样深
	for(int i=1;i<=k;i++)
	{		//只选择v+uv搭配
		f[u][i]+=f[v][i-1]+g[i];M(f[u][i]);
		pre[u][i]=pre[u][i-1]+f[u][i]; M(pre[u][i]);
	}
}

void dfs(int u,int fa)
{
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		if(v==fa)continue;
	    dfs(v,u);
		cal(u,v);
	}
	if(is[u]){	//u本身是关键点,那么子树内方案树加倍,因为加一个u结点仍满足条件
		f[u][0]=pre[u][0]=1;
		for(int i=1;i<=k;i++)
		{
			f[u][i]*=2;M(f[u][i]);
			pre[u][i]=pre[u][i-1]+f[u][i];M(pre[u][i]);
		}
	}
	if(u==1)ans+=pre[u][k];	//非根结点现在只算恰好为k的,别的后续更新。
	else ans+=f[u][k];
	M(ans);
}

int main()
{
	//freopen("input.in","r",stdin);
	int x,y;
	cin>>n>>m>>k;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		G[x].push_back(y);
		G[y].push_back(x);
	}
	for(int i=1;i<=m;i++)cin>>x,is[x]=1;
	dfs(1,0);
	cout<<ans<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值