题解:P4895 独钓寒江雪

题意

给定一棵无根树,求其中本质不同的独立集的个数。

思路

首先,由于要求统计独立集的个数,所以我们选定的点不能相邻。考虑 dp,设 \(f_{i,0}\) 表示 \(i\) 的子树内不选 \(i\) 的独立集个数,\(f_{i,1}\) 表示 \(i\) 的子树内选 \(i\) 的独立集个数。

接下来考虑如何处理本质不同的要求。对于点 \(i\),若它的某种同构子树有 \(x\) 个,而该种树的选点方案为 \(y\),则计算该种子树的贡献时可以视为将 \(x\) 个相同的树分配给 \(y\) 个不同的方案,分配的方案数为 \({x+y-1} \choose {x}\)。将各种同构子树的贡献相乘即可得到 \(f_{i,0/1}\)。

最后考虑如何处理无根树。对于无根树,我们可以以这棵树的重心为根进行计算。如果只有一个重心,记重心为 \(u\),则答案为 \(f_{i,0}+f_{i,1}\);如果有两个重心,记重心分别为 \(u,v\),将 \((u,v)\) 边断开,使整棵树分为两棵子树,再分别以 \(u,v\) 为根进行 dp,最后根据两棵子树是否同构进行计算,可以选 \(u\) 不选 \(v\),可以选 \(v\) 不选 \(u\),也可以两个都不选。若同构,则答案为 \(f_{i,0} \times f_{i,1}+ {{f_{i,0}+1} \choose 2}\);若不同构,则答案为 \(f_{u,1}\times f_{v,0}+f_{u,0}\times f_{v,1}+f_{u,0}\times f_{v,0}\)。

代码

#include<bits/stdc++.h>
#define int unsigned long long
#define MAXN 1000005
#define mod 1000000007
using namespace std;
int n,head[MAXN],cnt,f[MAXN][2],has[MAXN],base,inv[MAXN],siz[MAXN],rt[2],ans;
vector<int>v[MAXN];
struct Edge{
	int value,next;
}edge[MAXN];
void addedge(int u,int v){
	edge[++cnt].value=v;
	edge[cnt].next=head[u];
	head[u]=cnt;
}
int shift(int x){
	x^=base;
	x^=x>>7;
	x^=x<<13;
	x^=x>>11;
	x^=base;
	return x;
}
int fpow(int a,int b){
	int tans=1;
	while(b){
		if(b&1)tans=tans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return tans;
}
void init(){
	for(int i=1;i<=n;i++)inv[i]=fpow(i,mod-2);
}
int C(int n,int m){
	int tans=1;
	for(int i=n-m+1;i<=n;i++)tans=tans*i%mod;
	for(int i=1;i<=m;i++)tans=tans*inv[i]%mod;
	return tans;
}
bool cmp(int x,int y){
	return has[x]<has[y];
}
void find(int x,int fa){
	siz[x]=1;bool flag=1;
	for(int i=head[x];i;i=edge[i].next){
		int y=edge[i].value;
		if(y!=fa){
			find(y,x);
			siz[x]+=siz[y];
			if(siz[y]>n/2)flag=0;
		}
	}
	if(n-siz[x]<=n/2&&flag){
		if(!rt[0])rt[0]=x;
		else rt[1]=x;
	}
}
void dfs(int x,int fa){
	f[x][0]=f[x][1]=1;int tcnt=0;
	for(int i=head[x];i;i=edge[i].next){
		int y=edge[i].value;
		if(y!=fa&&!((x==rt[0]&&y==rt[1])||(x==rt[1]&&y==rt[0]))){//断开两个重心之间的边 
			v[x].push_back(y);
			tcnt++;
			dfs(y,x);
			has[x]=has[x]+shift(has[y]);
		}
	}
	sort(v[x].begin(),v[x].end(),cmp);int tmp=0;
	for(int i=0;i<tcnt;i++){
		int y=v[x][i];
		tmp++;//统计同种子树的数量 
		if(i==tcnt-1){
			f[x][0]=f[x][0]*C(f[y][0]+f[y][1]+tmp-1,tmp)%mod;
			f[x][1]=f[x][1]*C(f[y][0]+tmp-1,tmp)%mod;
			tmp=0;
		}else if(has[y]!=has[v[x][i+1]]){
			f[x][0]=f[x][0]*C(f[y][0]+f[y][1]+tmp-1,tmp)%mod;
			f[x][1]=f[x][1]*C(f[y][0]+tmp-1,tmp)%mod;
			tmp=0;
		}
	}
}
signed main(){
	srand(time(0));
	base=(rand()%30000+1)*(rand()%30000+1)*1331;
	scanf("%lld",&n);
	init();
	for(int i=1;i<n;i++){
		int u,v;
		scanf("%lld%lld",&u,&v);
		addedge(u,v),addedge(v,u);
	}
	find(1,-1);//找树的重心 
	if(!rt[1]){//若只有一个重心 
		dfs(rt[0],-1);
		ans=(f[rt[0]][0]+f[rt[0]][1])%mod;
	}
	else{//若有两个重心 
		dfs(rt[0],-1),dfs(rt[1],-1);
		if(has[rt[0]]==has[rt[1]])ans=(f[rt[0]][0]*f[rt[0]][1]%mod+C(f[rt[0]][0]+1,2))%mod;//两个子树同构 
		else ans=(f[rt[0]][1]*f[rt[1]][0]%mod+f[rt[0]][0]*f[rt[1]][1]%mod+f[rt[0]][0]*f[rt[1]][0]%mod)%mod;//两个子树不同构 
	}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2025-11-02 22:16  CharlieCai2024  阅读(31)  评论(0)    收藏  举报