题解:luogu P4895(独钓寒江雪)

1. Description

给定一棵树,询问这棵树有多少种本质不同的独立集。
我们称两个独立集本质相同,当我们可以对树进行重标号,使得边一一对应,且选择的点也一一对应。

2. Solution

我们发现,可以将树的重心作为根来统计,会省去很多讨论,这是很直觉的,也是很显然的。
此时考虑一个树形 dp,定义 \(f_{u,0/1}\) 表示 \(u\) 的子树中,\(u\) 未选择/选择的本质不同的独立集方案数。
会计算重复,只会在 \(u\) 的若干子树同构时出现这种情况。
因此,在计算 \(f_{u,0/1}\) 时,利用树哈希判断树同构,将所有同构的子树取出,记数量为 \(m\),选择子树根的方案数是 \(a\),不选择子树根的方案数是 \(b\),由于子树同构,所以他们 \(f_{v,0/1}\) 的值显然是相同的。
如果 \(u\) 选择,那么子树根就一定不选择,所以就相当于将完全相同的 \(m\) 个球染成 \(b\) 种颜色,询问方案数。
如果 \(u\) 不选择,那么子树根就可选可不选,所以就相当于将完全相同的 \(m\) 个球染成 \(a+b\) 种颜色,询问方案数。
很显然,可以利用插板法求解,将 \(m\) 个球染成 \(x\) 种颜色的方案数是 \(C_{m+x-1}^{x-1}=C_{m+x-1}^{m}\),直接计算下降幂,预处理阶乘逆元即可在 \(O(m)\) 的时间复杂度内计算组合数。
最后可以得到总时间复杂度为 \(O(n)\) 的算法。
注意存在两个重心时的讨论,如果两个重心对应的子树同构,那么答案就是 \(f_{x,0}\times f_{x,1}+C_{f_{x,0}+1}^{2}\),否则答案是 \(f_{x,0}\times f_{y,0}+f_{x,1}\times f_{y,0}+f_{x,0}\times f_{y,1}\)

3. Code

/*by ChenMuJiu*/
/*略去缺省源与快读快写*/
const int N=5e5+5,mod=1e9+7;
int n;
int prime[N],siz[N],f[N],g[N][2];
int fac[N],inv[N];
ull hsh[N];
vector<int>e[N];
int add(int x,int y){
	x+=y;
	return x>=mod?x-mod:x;
}
int sub(int x,int y){
	x-=y;
	return x<0?x+mod:x;
}
int mul(int x,int y){
	long long res=1ll*x*y;
	return res>=mod?res%mod:res;
}
int binpow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=mul(res,a);
		b>>=1;
		a=mul(a,a);
	}
	return res;
}
namespace TreeHash{
const int M=75e5+5;
bool flag[M];
void init(){
	int cnt=0;
	for(int i=2;i<=7500000;i++){
		if(!flag[i])
			prime[++cnt]=i;
        if(cnt==n)break;
		for(int j=1;j<=cnt&&prime[j]*i<=7500000;j++){
            flag[prime[j]*i]=1;
			if(i%prime[j]==0)break;
		}
	}
}
}
void init(int n){
	fac[0]=1;
	for(int i=1;i<=n;i++)
		fac[i]=mul(fac[i-1],i);
	inv[n]=binpow(fac[n],mod-2);
	for(int i=n-1;i>=0;i--)
		inv[i]=mul(inv[i+1],i+1);
}
void findrt(int u,int fa){
	siz[u]=1;
	for(int v:e[u]){
		if(v==fa)continue;
		findrt(v,u);
		tomax(f[u],siz[v]);
		siz[u]+=siz[v];
	}
	tomax(f[u],n-siz[u]);
}
bool cmp(int x,int y){
	return hsh[x]<hsh[y];
}
int C(int m,int n){
	int res=1;
	for(int i=1,x=n;i<=m;i++){
		res=mul(res,x);
		x=sub(x,1);
	}
	return mul(res,inv[m]);
}
void dfs(int u,int fa){
	siz[u]=1;
	for(int v:e[u]){
		if(v==fa)continue;
		dfs(v,u);
	}
	vector<int>son;
	hsh[u]=1;
	for(int v:e[u]){
		if(v==fa)continue;
		son.push_back(v);
		siz[u]+=siz[v];
		hsh[u]+=hsh[v]*prime[siz[v]];
	}
	hsh[u]*=siz[u];
	sort(son.begin(),son.end(),cmp);
	g[u][0]=g[u][1]=1;
	int cnt=son.size();
	for(int i=0;i<cnt;){
		int j=i;
		while(j+1<cnt&&hsh[son[i]]==hsh[son[j+1]])
			j++;
		int tmp=j-i+1,v=son[i];
		g[u][0]=mul(g[u][0],C(tmp,add(add(g[v][0],g[v][1]),tmp-1)));
		g[u][1]=mul(g[u][1],C(tmp,add(g[v][0],tmp-1)));
		i=j+1;
	}
}
signed main(){
	read(n);
	for(int i=2,u,v;i<=n;i++){
		read(u),read(v);
		e[u].push_back(v);
		e[v].push_back(u);
	}
	TreeHash::init();
	init(n);
	findrt(1,0);
	int mi=n+1;
	for(int i=1;i<=n;i++)
		tomin(mi,f[i]);
	int cnt=0,ans;
	for(int i=1;i<=n;i++)
		if(f[i]==mi)
			cnt++;
	if(cnt==1){
		int rt=-1;
		for(int i=1;i<=n;i++)
			if(f[i]==mi)rt=i;
		dfs(rt,0);
		ans=add(g[rt][0],g[rt][1]);
	}else{
		int rt1=-1,rt2=-1;
		for(int i=1;i<=n;i++)
			if(f[i]==mi){
				if(rt1==-1)rt1=i;
				else rt2=i;
			}
		dfs(rt1,rt2);
		dfs(rt2,rt1);
		if(hsh[rt1]==hsh[rt2])
			ans=add(mul(g[rt1][0],g[rt1][1]),add(C(2,g[rt1][0]),g[rt1][0]));
		else
			ans=add(add(mul(g[rt1][0],g[rt2][1]),mul(g[rt1][1],g[rt2][0])),mul(g[rt1][0],g[rt2][0]));
	}
	write(ans);
}
posted @ 2026-05-14 14:10  陈牧九  阅读(10)  评论(0)    收藏  举报