题目描述
刚刚解决完电力网络的问题,阿狸又被领导的任务给难住了。
刚才说过,阿狸的国家有 n n n 个城市,现在国家需要在某些城市对之间建立一些贸易路线,使得整个国家的任意两个城市都直接或间接的连通。
为了省钱, 每两个城市之间最多只能有一条直接的贸易路径。对于两个建立路线的方案,如果存在一个城市对,在两个方案中是否建立路线不一样,那么这两个方案就是不同的,否则就是相同的。现在你需要求出一共有多少不同的方案。
好了,这就是困扰阿狸的问题。换句话说,你需要求出 n n n 个点的简单 (无重边无自环) 有标号无向连通图数目。
由于这个数字可能非常大, 你只需要输出方案数对 1004535809 1004535809 1004535809 ( 479 × 2 21 + 1 479 \times 2 ^{21} + 1 479×221+1 ) 即可。
1 ≤ n ≤ 130000 1\le n\le130000 1≤n≤130000
题解
大力推式子
设 g ( n ) g(n) g(n) 为 n n n 个点的简单有标号无向图数目。
相当于有 n ( n − 1 ) 2 \dfrac{n(n-1)}2 2n(n−1) 条边,选或不选,可得 g ( n ) = 2 n ( n − 1 ) 2 g(n)=2^{\frac{n(n-1)}2} g(n)=22n(n−1)
设 f ( n ) f(n) f(n) 为 n n n 个点的简单有标号无向连通图数目。
想要求连通图数目,可以用所有的减去不连通的。
如何求不连通图的数目呢?
我们可以递归考虑。首先确定一个顺序,把第一个点固定。由于不连通图至少有两个连通块,所以枚举包括第一个点的连通块点的数量 i i i,范围为 1 ∼ n − 1 1\sim n-1 1∼n−1,其他的点随意选,还有要在 ( n − 1 ) (n-1) (n−1) 个点选 ( i − 1 ) (i-1) (i−1) 个(第一个点固定)。方案如下。
f ( n ) = g ( n ) − ∑ i = 1 n − 1 f ( i ) g ( n − i ) ( n − 1 i − 1 ) f(n)=g(n)-\sum\limits_{i=1}^{n-1}f(i)g(n-i)\dbinom{n-1}{i-1} f(n)=g(n)−i=1∑n−1f(i)g(n−i)(i−1n−1)
把组合数拆开得
f
(
n
)
=
g
(
n
)
−
(
n
−
1
)
!
∑
i
=
1
n
−
1
f
(
i
)
(
i
−
1
)
!
g
(
n
−
i
)
(
n
−
i
)
!
f(n)=g(n)-(n-1)!\sum\limits_{i=1}^{n-1}\dfrac{f(i)}{(i-1)!}\dfrac{g(n-i)}{(n-i)!}
f(n)=g(n)−(n−1)!i=1∑n−1(i−1)!f(i)(n−i)!g(n−i)
观察到式子后半部分类似于卷积的形式,想办法把它构造成标准形式,就是把 i i i 的枚举范围变成 0 ∼ n 0\sim n 0∼n, ( i − 1 ) ! (i-1)! (i−1)! 转换为 i ! i! i!。
当 i = 0 i=0 i=0 时, ( i − 1 ) ! (i-1)! (i−1)! 无意义,所以可以把它变成 i ! i \dfrac{i!}{i} ii!,这样结果就是 0 0 0 了,不会造成影响。
当 i = n i=n i=n 是,卷积为 f ( n ) ( n − 1 ) ! \dfrac{f(n)}{(n-1)!} (n−1)!f(n)。
因此
g
(
n
)
(
n
−
1
)
!
=
∑
i
=
0
n
f
(
i
)
i
i
!
g
(
n
−
i
)
(
n
−
i
)
!
\dfrac{g(n)}{(n-1)!}=\sum\limits_{i=0}^{n}\dfrac{f(i)i}{i!}\dfrac{g(n-i)}{(n-i)!}
(n−1)!g(n)=i=0∑ni!f(i)i(n−i)!g(n−i)
根据套路,构造 F ( x ) = ∑ i = 0 ∞ f ( i ) i i ! x i , G ( x ) = ∑ i = 0 ∞ g ( i ) i ! x i F(x)=\sum\limits_{i=0}^{\infty}\dfrac{f(i)i}{i!}x^i,G(x)=\sum\limits_{i=0}^{\infty}\dfrac{g(i)}{i!}x^i F(x)=i=0∑∞i!f(i)ixi,G(x)=i=0∑∞i!g(i)xi
则
F
(
x
)
⋅
G
(
x
)
=
∑
i
=
0
∞
x
i
∑
j
+
k
=
i
f
(
j
)
j
j
!
g
(
k
)
(
k
)
!
=
∑
i
=
0
∞
g
(
i
)
(
i
−
1
)
!
x
i
\begin{aligned} F(x)\cdot G(x)&=\sum\limits_{i=0}^{\infty}x^i\sum\limits_{j+k=i}\dfrac{f(j)j}{j!}\dfrac{g(k)}{(k)!}\\ &=\sum\limits_{i=0}^{\infty}\dfrac{g(i)}{(i-1)!}x^i \end{aligned}
F(x)⋅G(x)=i=0∑∞xij+k=i∑j!f(j)j(k)!g(k)=i=0∑∞(i−1)!g(i)xi
令 H ( x ) = ∑ i = 0 ∞ g ( i ) ( i − 1 ) ! x i H(x)=\sum\limits_{i=0}^{\infty}\dfrac{g(i)}{(i-1)!}x^i H(x)=i=0∑∞(i−1)!g(i)xi
可得 F ( x ) ⋅ G ( x ) = H ( x ) F(x)\cdot G(x)=H(x) F(x)⋅G(x)=H(x)
两边同时模 x n + 1 x^{n+1} xn+1
得到 F ( x ) ⋅ G ( x ) ≡ H ( x ) ( m o d x n + 1 ) F(x)\cdot G(x)\equiv H(x)\pmod{x^{n+1}} F(x)⋅G(x)≡H(x)(modxn+1)
很容易就得到 F ( x ) ≡ H ( x ) G ( x ) ( m o d x n + 1 ) F(x)\equiv\dfrac{H(x)}{G(x)}\pmod{x^{n+1}} F(x)≡G(x)H(x)(modxn+1)
H ( x ) , G ( x ) H(x),G(x) H(x),G(x) 都是已知的,使用多项式求逆,多项式乘法就可以求出 F ( x ) F(x) F(x)。
答案即为 F ( x ) F(x) F(x) 次数为 n n n 的项的系数乘 ( i − 1 ) ! (i-1)! (i−1)!。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=(1<<18)+1;
const ll mod=1004535809,g=3;
int len=1,n;
ll w,wn,G1[N],G[N],invG[N],f[N],a1[N],inv[N];
ll ksm(ll a,ll b)
{
ll ans=1;
while(b){
if(b&1) ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
void change(ll num[])
{
for(int i=1,j=len/2;i<len-1;i++){
if(i<j) swap(num[i],num[j]);
int k=len/2;
while(j>=k) j-=k,k>>=1;
if(j<k) j+=k;
}
}
void ntt(ll num[],int fl)
{
for(int i=2;i<=len;i<<=1){
if(fl==1) wn=ksm(g,(mod-1)/i);
else wn=ksm(g,mod-1-(mod-1)/i);
for(int j=0;j<len;j+=i){
w=1;
for(int k=j;k<j+i/2;k++){
ll u=w*num[k+i/2]%mod,t=num[k];
num[k]=(t+u)%mod;
num[k+i/2]=(t-u+mod)%mod;
w=w*wn%mod;
}
}
}
if(fl==-1){
ll inv=ksm(len,mod-2);
for(int i=0;i<len;i++) num[i]=num[i]*inv%mod;
}
}
int read()
{
int sum=0,c=getchar();
while(c<48||c>57) c=getchar();
while(c>=48&&c<=57) sum=sum*10+c-48,c=getchar();
return sum;
}
void solve(int n,ll a[],ll ans[])
{
if(n==1){ans[0]=ksm(a[0],mod-2);return;}
solve((n+1)/2,a,ans);
len=1;
while(len<2*n) len*=2;
for(int i=0;i<n;i++) a1[i]=a[i];
for(int i=n;i<len;i++) a1[i]=0;
change(a1),change(ans);
ntt(a1,1),ntt(ans,1);
for(int i=0;i<len;i++) ans[i]=ans[i]*(2-ans[i]*a1[i]%mod+mod)%mod;
change(ans),ntt(ans,-1);
for(int i=n;i<len;i++) ans[i]=0;
}
int main()
{
scanf("%d",&n);
f[0]=1,G1[0]=0,G[0]=1;
for(int i=1;i<=n;i++) f[i]=f[i-1]*i%mod;
inv[n]=ksm(f[n],mod-2);
for(int i=n-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
for(int i=1;i<=n;i++) G1[i]=ksm(2,1ll*i*(i-1)/2),G[i]=G1[i]*inv[i]%mod,G1[i]=G1[i]*i%mod*inv[i]%mod;
solve(n+1,G,invG);
len=1;
while(len<(n+1)*2) len*=2;
change(G1),ntt(G1,1);
change(invG),ntt(invG,1);
for(int i=0;i<len;i++) G1[i]=G1[i]*invG[i]%mod;
change(G1),ntt(G1,-1);
printf("%lld\n",G1[n]*f[n-1]%mod);
}