BZOJ3162 独钓寒江雪
题意
求一棵无根树上本质不同的独立集个数。答案对\(10^9+7\)取模。
\(n \leq 500000\)
分析
如果只求树上独立集个数的话,设\(dp[x][0/1]\)表示以\(x\)为根的子树中选或不选\(x\)的方案数。有显然的转移:
\[dp[x][0]=\prod_{y \in son(x)}(dp[y][0]+dp[y][1]) 就是所有子树中选与不选和的乘积 \\
dp[x][1]=\prod_{y \in son(x)}dp[y][0] 就是所有子树中不选的乘积
\]
现在问题在于本质不同。那么如果重新标号之后同构的话方案数就会多算。
考虑重新标号后重心不会变,于是以重心为根处理子树。如果两个点为根是同构的,那么他们的重心相同,所以直接找出树的重心,以重心为根进行转移。
提前预处理每一棵子树的哈希值,因为相同的子树是同构的,所以转移相当于是可重组合的计算。
一个组合数学的相关知识:有\(n\)种物品要求从中选\(m\)个求问方案数,那么就是\(\binom{n+m-1}{m}\)。
这题要求求本质不同的独立集个数,这时候就需要做的时候把所有本质相同的看作一个子树,那么就相当于在这些本质相同子树中选它们的数量那么多个,利用上面的组合数学相关知识即可解决。由于结构不同的子树再怎么弄都不会成为本质相同的,所以我们将结构相同的子树分成一块。
如果有两个重心就分别DP,最后合并的时候讨论一下就行了。
时间复杂度\(O(n)\)。
代码
#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read()
{
rg T data=0;
rg int w=1;
rg char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-') w=-1;
ch=getchar();
}
while(isdigit(ch))
{
data=data*10+ch-'0';
ch=getchar();
}
return data*w;
}
template<class T>il T read(rg T&x)
{
return x=read<T>();
}
typedef unsigned long long ll;
using namespace std;
co int N=5e5+1,P=233,mod=1e9+7;
ll p[N];
int n,inv[N];
int add(int x,int y)
{
x+=y;
return x>=mod?x-mod:x;
}
int mul(int x,int y)
{
return (ll)x*y%mod;
}
int calc(int n,int m)
{
int ans=1;
for(int i=m;i;--i)
ans=mul(ans,mul(inv[i],n+1-i));
return ans;
}
vector<int>g[N];
int siz[N],G1,G2;
void get_root(int x,int fa)
{
siz[x]=1;
bool flag=1;
for(int i=0;i<g[x].size();++i)
{
int y=g[x][i];
if(y==fa) continue;
get_root(y,x);
siz[x]+=siz[y];
if(siz[y]*2>n) flag=0;
}
if(siz[x]*2<n) flag=0;
if(flag) !G1?G1=x:G2=x;
}
int q[N],top,dp[N][2];
ll hs[N];
bool cmp(co int&a,co int&b)
{
return hs[a]<hs[b];
}
void dfs(int x,int fa)
{
hs[x]=1,siz[x]=1;
for(int i=0;i<g[x].size();++i)
{
int y=g[x][i];
if(y==fa) continue;
dfs(y,x);
siz[x]+=siz[y],hs[x]+=p[siz[y]]*hs[y];
}
top=0; // edit 1: enqueue later because the use of q
for(int i=0;i<g[x].size();++i)
{
int y=g[x][i];
if(y==fa) continue;
q[++top]=y;
}
sort(q+1,q+top+1,cmp);
dp[x][0]=dp[x][1]=1;
for(int i=1,j;i<=top;i=j)
{
for(j=i+1;j<=top&&hs[q[i]]==hs[q[j]];++j);
int v=q[i];
dp[x][0]=mul(dp[x][0],calc(add(dp[v][0],add(dp[v][1],j-i-1)),j-i));
dp[x][1]=mul(dp[x][1],calc(add(dp[v][0],j-i-1),j-i));
}
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(n);
p[0]=inv[0]=inv[1]=1,p[1]=P;
for(int i=2;i<=n;++i)
inv[i]=mul(inv[mod%i],mod-mod/i),p[i]=p[i-1]*P;
for(int i=1;i<n;++i)
{
int x=read<int>(),y=read<int>();
g[x].push_back(y),g[y].push_back(x);
}
get_root(1,1);
G2?(dfs(G1,G2),dfs(G2,G1)):dfs(G1,G1);
int ans;
if(G2)
{
if(hs[G1]!=hs[G2])
ans=add(mul(dp[G1][0],dp[G2][1]),add(mul(dp[G1][1],dp[G2][0]),mul(dp[G1][0],dp[G2][0])));
else
ans=add(mul(dp[G1][0],dp[G1][1]),calc(add(dp[G1][0],1),2))%mod;
}
else
ans=add(dp[G1][0],dp[G1][1]);
printf("%d\n",ans);
return 0;
}
/*
5
1 2
1 3
1 4
1 5
*/
静渊以有谋,疏通而知事。