背景:
luogu
\text{luogu}
luogu推荐了这道水题,蒟蒻都能很快切。
题目传送门:
https://www.luogu.org/problem/P4841
题意:
求
n
n
n个点的无向连通图的个数。
思路:
这是
luogu P4233
\text{luogu P4233 }
luogu P4233 射命丸文的笔记的弱化版。
考虑图的总数(不考虑是否连通)
g
n
=
2
n
(
n
−
1
)
2
g_n=2^{\frac{n(n-1)}{2}}
gn=22n(n−1),
n
(
n
−
1
)
2
\frac{n(n-1)}{2}
2n(n−1)条边,每一条边都可以选或不选。
设
f
n
f_n
fn表示
n
n
n个点的无向连通图的个数。
i
i
i枚举连通的那一个连通块的大小,显然有:
g
n
=
∑
i
=
1
n
C
n
−
1
i
−
1
f
i
g
n
−
i
g_n=\sum_{i=1}^{n}C_{n-1}^{i-1}f_{i}g_{n-i}
gn=i=1∑nCn−1i−1fign−i
理解起来就是从剩下的
n
−
1
n-1
n−1个点选择
i
−
1
i-1
i−1个点连到这个连通块(这个连通块原来必然有一个点,一个点也是一个连通块);连通块的大小为
i
i
i的贡献是
f
i
f_i
fi;剩下的
n
−
i
n-i
n−i个点可以随便乱连,只要不连到选中的
i
i
i个点中,方案数为
g
n
−
i
g_{n-i}
gn−i。
化简一下,有:
g n = ∑ i = 1 n ( n − 1 ) ! ( i − 1 ) ! ( n − i ) ! f i g n − i g_n=\sum_{i=1}^{n}\frac{(n-1)!}{(i-1)!(n-i)!}f_ig_{n-i} gn=i=1∑n(i−1)!(n−i)!(n−1)!fign−i
g n ( n − 1 ) ! = ∑ i = 1 n f i ( i − 1 ) ! g n − i ( n − i ) ! \frac{g_n}{(n-1)!}=\sum_{i=1}^{n}\frac{f_i}{(i-1)!}\frac{g_{n-i}}{(n-i)!} (n−1)!gn=i=1∑n(i−1)!fi(n−i)!gn−i
设
F
i
=
f
i
(
i
−
1
)
!
,
G
i
=
g
i
i
!
,
H
i
=
g
i
(
i
−
1
)
!
F_i=\frac{f_i}{(i-1)!},G_i=\frac{g_{i}}{i!},H_i=\frac{g_{i}}{(i-1)!}
Fi=(i−1)!fi,Gi=i!gi,Hi=(i−1)!gi
考虑生成函数的表示。
H
=
F
G
H=FG
H=FG
F = H G F=\frac{H}{G} F=GH
多项式求逆即可。
最后
f
i
=
F
i
∗
(
i
−
1
)
!
f_i=F_i*(i-1)!
fi=Fi∗(i−1)!,即可。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
const int mod=1004535809,G=3,inv_G=334845270;
using namespace std;
int a[1000010],b[1000010],f[1000010],g[1000010],h[1000010],fac[1000010],Inv[1000010];
int limit,n,l,r[1000010];
int dg(int x,LL k)
{
if(!k) return 1;
int op=dg(x,k>>1);
if(k&1) return (LL)op*op%mod*x%mod; else return (LL)op*op%mod;
}
int inv(int x)
{
return dg(x,mod-2);
}
void init(int n)
{
limit=1,l=0;
while(limit<(n<<1))
limit<<=1,l++;
for(int i=1;i<limit;i++)
r[i]=((r[i>>1]>>1)|((i&1)<<(l-1)));
}
void NTT(int *now,int limit,int op)
{
for(int i=0;i<limit;i++)
if(i<r[i]) swap(now[i],now[r[i]]);
for(int mid=1;mid<limit;mid<<=1)
{
int wn=dg(op==1?G:inv_G,(mod-1)/(mid<<1));
for(int j=0;j<limit;j+=(mid<<1))
{
int w=1;
for(int k=0;k<mid;k++,w=((LL)w*wn)%mod)
{
int x=now[j+k],y=(LL)w*now[j+k+mid]%mod;
now[j+k]=(x+y)%mod;
now[j+k+mid]=(x-y+mod)%mod;
}
}
}
}
void dft(int *f,int n,int limit)
{
NTT(f,limit,-1);
int INV=inv(limit);
for(int i=0;i<n;i++)
f[i]=(LL)f[i]*INV%mod;
}
void poly_inv(int *f,int *g,int n)
{
if(n==1)
{
g[0]=inv(f[0]);
return;
}
poly_inv(f,g,(n+1)>>1);
init(n);
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
for(int i=0;i<n;i++)
a[i]=f[i],b[i]=g[i];
NTT(a,limit,1),NTT(b,limit,1);
for(int i=0;i<limit;i++)
b[i]=(LL)b[i]*((2ll-(LL)a[i]*b[i]%mod+mod)%mod)%mod;
dft(b,n,limit);
for(int i=0;i<n;i++)
g[i]=b[i];
}
void INIT(int n)
{
fac[0]=fac[1]=1;
Inv[0]=Inv[1]=1;
for(int i=2;i<=n;i++)
{
fac[i]=(LL)fac[i-1]*i%mod;
Inv[i]=((LL)mod-mod/i)*Inv[mod%i]%mod;
}
for(int i=1;i<=n;i++)
Inv[i]=(LL)Inv[i-1]*Inv[i]%mod;
}
int main()
{
scanf("%d",&n);
INIT(n);
g[0]=1;
for(int i=1;i<=n;i++)
{
g[i]=(LL)dg(2,(LL)i*(i-1)/2)*Inv[i]%mod;
h[i]=(LL)dg(2,(LL)i*(i-1)/2)*Inv[i-1]%mod;
}
poly_inv(g,f,n+1);
init(n+1);
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
for(int i=0;i<=n;i++)
a[i]=f[i],b[i]=h[i];
NTT(a,limit,1),NTT(b,limit,1);
for(int i=0;i<limit;i++)
f[i]=(LL)a[i]*b[i]%mod;
dft(f,n+1,limit);
printf("%d",(LL)f[n]*fac[n-1]%mod);
}