【长链剖分】【DP】BZOJ4543[POI2014]Hotel加强版

该博客介绍了如何解决一个图论问题:在具有n个节点的无根树中找到三个互不相同节点,使得它们两两之间的距离相等。博主通过动态规划(DP)策略来解决这个问题,定义了f(x,i)和g(x,i)两个状态,并详细解释了状态转移方程。最后,博主提出了一种利用最大儿子的DP值在O(n)时间内枚举轻儿子最大深度的方法,以实现整体O(n)的时间复杂度解决方案。" 130419932,8178746,搭建pikachu:快速指南,"['服务器', '运维', '数据库管理', 'Web应用']

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

题意:

给出一棵 n 个点的无根树,请在这棵树上选三个互不相同的节点,使得这个三个
节点两两之间距离相等,输出方案数即可。


分析:

定义
f ( x , i ) f(x,i) f(x,i)表示在以x为根的子树中,与x距离为i的节点数
g ( x , i ) g(x,i) g(x,i)表示在以x为根的子树中选择了两个节点,最后一个点需满足与x的距离为i的方案数。
所以 f ( x , i ) = ∑ u f ( u , i − 1 ) f(x,i)=\sum_u f(u,i-1) f(x,i)=uf(u,i1)
g ( x , i ) = ( ∑ u g ( u , i + 1 ) ) + ( ∑ u , v f ( u , i ) ∗ f ( v , i ) ) g(x,i)=(\sum_u g(u,i+1))+(\sum_{u,v}f(u,i)*f(v,i)) g(x,i)=(ug(u,i+1))+(u,vf(u,i)f(v,i))

可以通过继承其最大儿子的DP值,使其最大儿子的转移速度为O(1),再O(n)暴力枚举每个轻儿子的最大深度。

继承的具体操作,可以通过预开内存的方式实现。
(即建立一个内存池,然后f(x)从 i i i位置开始, f ( s o n x ) f(son_x) f(sonx)就从 i + 1 i+1 i+1位置开始,g(x)从i位置开始, g ( s o n x ) g(son_x) g(sonx)就从i-1位置开始,详见代码中两个dfs)

总的复杂度为 O ( ∑ 重 链 长 度 ) = O ( n ) O(\sum 重链长度)=O(n) O()=O(n)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define SF scanf
#define PF printf
#define MAXN 100010
using namespace std;
typedef long long ll;
vector<int> a[MAXN];
ll Gpool[MAXN*2];
ll Fpool[MAXN];
ll *f[MAXN],*g[MAXN],*fcnt=Fpool,*gcnt=Gpool;
int maxl[MAXN],son[MAXN];
void prepare(int x,int fa=0){
	for(int i=0;i<int(a[x].size());i++){
		int u=a[x][i];
		if(u==fa)
			continue;
		prepare(u,x);
		maxl[x]=max(maxl[x],maxl[u]+1);
	}
	for(int i=0;i<int(a[x].size());i++){
		int u=a[x][i];
		if(u==fa)
			continue;
		if(son[x]==0||maxl[u]>maxl[son[x]])
			son[x]=u;
	}
}
void dfsf(int x,int fa=0){
	f[x]=++fcnt;
	if(son[x])
		dfsf(son[x],x);
	for(int i=0;i<int(a[x].size());i++){
		int u=a[x][i];
		if(u==fa||u==son[x])
			continue;
		dfsf(u,x);
	}
}
void dfsg(int x,int fa=0){
	for(int i=0;i<int(a[x].size());i++){
		int u=a[x][i];
		if(u==fa||u==son[x])
			continue;
		dfsg(u,x);
		gcnt+=maxl[u];
	}
	if(son[x])
		dfsg(son[x],x);
	g[x]=++gcnt;
}
ll ans;
void dp(int x,int fa=0){
	f[x][0]=1;
	if(son[x]==0)
		return ;
	dp(son[x],x);
	ans+=g[x][0];
	for(int i=0;i<int(a[x].size());i++){
		int u=a[x][i];
		if(u==fa||u==son[x])
			continue;
		dp(u,x);	
		for(int i=1;i<=maxl[u]+1;i++)
			ans+=g[x][i]*f[u][i-1];
		for(int i=0;i<=maxl[u]-1;i++)
			ans+=f[x][i]*g[u][i+1];
		for(int i=0;i<=maxl[u]-1;i++)
			g[x][i]+=g[u][i+1];
		for(int i=1;i<=maxl[u]+1;i++)
			g[x][i]+=(f[u][i-1]*f[x][i]);
		for(int i=1;i<=maxl[u]+1;i++)
			f[x][i]+=f[u][i-1];
	}
//	PF("%d %d(%lld):\n",x,maxl[x],ans);
//	for(int i=0;i<=maxl[x];i++)
//		PF("%d:[%lld %lld]\n",i,f[x][i],g[x][i]);
//	PF("-------------------\n");
}
int n,u,v;
void init(){
	fcnt=Fpool;
	gcnt=Gpool;
	memset(Fpool,0,sizeof Fpool);
	memset(Gpool,0,sizeof Gpool);
	memset(maxl,0,sizeof maxl);
	memset(son,0,sizeof son);
	for(int i=1;i<=n;i++)
		a[i].clear();
	ans=0;	
}
int main(){
//	freopen("three1-3.in","r",stdin);
	while(SF("%d",&n)!=EOF){
		if(n==0)
			break;
		init();
		for(int i=1;i<n;i++){
			SF("%d%d",&u,&v);
			a[u].push_back(v);
			a[v].push_back(u);	
		}
		prepare(1);
		dfsf(1);
		dfsg(1);
		dp(1);
		PF("%lld\n",ans);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值