传送门
题意就是求
n
n
n个点的无向图方案数(无重边无自环)
有三种做法,亲自试了前两种
首先是多项式求逆(好像也是普遍做法?
先来推一推柿子
q
w
q
qwq
qwq
设
f
[
i
]
f[i]
f[i]为
i
i
i个点的方案数,若不考虑不合法情况,
f
[
i
]
=
2
C
i
2
f[i]=2^{C_i^2}
f[i]=2Ci2
若考虑不合法情况,就是有不连通的情况,因为所有点的本质都是一样的,所以我们只需要规定一个点为基准点,假设为
1
1
1
那么
1
1
1号点的联通块大小可能为
1
≤
j
≤
i
−
1
1\le j\le i-1
1≤j≤i−1,在联通块内的点有
C
i
−
1
j
−
1
C_{i-1}^{j-1}
Ci−1j−1种,其他不与
1
1
1相连的有
2
C
i
−
j
2
2^{C_{i-j}^2}
2Ci−j2种连法,所以总的就是:
f
[
i
]
=
2
C
i
2
−
∑
j
=
1
i
−
1
f
[
j
]
×
C
i
−
1
j
−
1
×
2
C
i
−
j
2
f[i]=2^{C_i^2}-\sum_{j=1}^{i-1} f[j]\times C_{i-1}^{j-1}\times 2^{C_{i-j}^2}
f[i]=2Ci2−∑j=1i−1f[j]×Ci−1j−1×2Ci−j2
两边同除
(
i
−
1
)
!
(i-1)!
(i−1)!
神奇地变到了
f
[
i
]
(
i
−
1
)
!
=
2
C
i
2
(
i
−
1
)
!
−
∑
j
=
1
i
−
1
f
[
j
]
(
j
−
1
)
!
×
2
C
i
−
j
2
(
i
−
j
)
!
\frac{f[i]}{(i-1)!}=\frac{2^{C_i^2}}{(i-1)!}-\sum_{j=1}^{i-1} \frac{f[j]}{(j-1)!}\times \frac{2^{C_{i-j}^2}}{(i-j)!}
(i−1)!f[i]=(i−1)!2Ci2−∑j=1i−1(j−1)!f[j]×(i−j)!2Ci−j2
这就长得很卷积了
然后移项合并,就变成:
∑
j
=
1
i
f
[
j
]
(
j
−
1
)
!
×
2
C
i
−
j
2
(
i
−
j
)
!
=
2
C
i
2
(
i
−
1
)
!
\sum_{j=1}^i \frac{f[j]}{(j-1)!}\times \frac{2^{C_{i-j}^2}}{(i-j)!}=\frac{2^{C_i^2}}{(i-1)!}
∑j=1i(j−1)!f[j]×(i−j)!2Ci−j2=(i−1)!2Ci2
令
A
=
∑
j
=
1
n
f
[
j
]
(
j
−
1
)
!
x
j
A=\sum_{j=1}^n \frac{f[j]}{(j-1)!} x^j
A=∑j=1n(j−1)!f[j]xj
B = ∑ j = 0 n 2 C j 2 j ! x j B=\sum_{j=0}^n \frac{2^{C_{j}^2}}{j!} x^j B=∑j=0nj!2Cj2xj
C = ∑ j = 1 n 2 C j 2 ( j − 1 ) ! x j C=\sum_{j=1}^n \frac{2^{C_{j}^2}}{(j-1)!} x^j C=∑j=1n(j−1)!2Cj2xj
A
∗
B
≡
C
m
o
d
x
n
+
1
A*B\equiv C\ mod\ x^{n+1}
A∗B≡C mod xn+1
那么
A
≡
C
∗
B
−
1
m
o
d
x
n
+
1
A\equiv C*B^{-1}\ mod\ x^{n+1}
A≡C∗B−1 mod xn+1
然后就是多项式求逆了
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define maxn 600005
#define LL long long
using namespace std;
int n,a[maxn],b[maxn],rev[maxn],tmp[maxn],fac[maxn],inv[maxn],bin[maxn];
const int mod=1004535809;
inline int qpow(int x,int k){
int ret=1;
while(k){
if(k&1) ret=1LL*ret*x%mod;
x=1LL*x*x%mod; k>>=1;
} return ret;
}
inline void NTT(int *F,int type,int limit){
for(int i=0;i<limit;i++)
if(i<rev[i]) swap(F[i],F[rev[i]]);
for(int mid=1;mid<limit;mid<<=1){
int Wn=qpow(3,type==1?(mod-1)/(mid<<1):(mod-1-(mod-1)/(mid<<1)));
for(int r=mid<<1,j=0;j<limit;j+=r){
int w=1;
for(int k=0;k<mid;k++,w=1LL*Wn*w%mod){
int x=F[j+k],y=1LL*w*F[j+mid+k]%mod;
F[j+k]=(x+y)%mod; F[j+mid+k]=(x-y+mod)%mod;
}
}
}
if(type==-1){
int Inv=qpow(limit,mod-2);
for(int i=0;i<limit;i++) F[i]=1LL*F[i]*Inv%mod;
}
}
inline void INV(int *a,int *b,int limit){
if(limit==1){
b[0]=qpow(a[0],mod-2);
return;
}
INV(a,b,limit>>1);
memcpy(tmp,a,sizeof(int)*limit); memset(tmp+limit,0,sizeof(int)*limit);
int lim=0; while(!(limit>>lim&1)) ++lim;
for(int i=0;i<(limit<<1);i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<lim);
NTT(tmp,1,limit<<1); NTT(b,1,limit<<1);
for(int i=0;i<(limit<<1);i++)
tmp[i]=(2LL*b[i]%mod+mod-1LL*tmp[i]*b[i]%mod*b[i]%mod)%mod;
NTT(tmp,-1,limit<<1);
memcpy(b,tmp,sizeof(int)*limit); memset(b+limit,0,sizeof(int)*limit);
}
inline void MUL(int *a,int *b,int limit){
int lim=0; while(!(limit>>lim&1)) ++lim;
for(int i=0;i<limit;i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(lim-1));
NTT(a,1,limit); NTT(b,1,limit);
for(int i=0;i<limit;i++) a[i]=1LL*a[i]*b[i]%mod;
NTT(a,-1,limit);
}
int main(){
scanf("%d",&n);
fac[0]=1; for(int i=1;i<=n;i++) fac[i]=1LL*fac[i-1]*i%mod;
inv[n]=qpow(fac[n],mod-2);
for(int i=n;i;i--) inv[i-1]=1LL*inv[i]*i%mod;
for(int i=0;i<=n;i++)
bin[i]=qpow(2,(1LL*i*(i-1)>>1)%(mod-1));
for(int i=0;i<=n;i++) a[i]=1LL*bin[i]*inv[i]%mod;
int limit=1; while(limit<=n) limit<<=1;
INV(a,b,limit); memset(a,0,sizeof a);
for(int i=1;i<=n;i++) a[i]=1LL*bin[i]*inv[i-1]%mod;
MUL(a,b,limit<<1);
printf("%d\n",1LL*a[n]*fac[n-1]%mod);
return 0;
}
第二种就是分治
F
F
T
FFT
FFT(其实是
N
T
T
NTT
NTT但叫习惯了就这么叫吧?
就是把上面最终推出来的式子:
∑
j
=
1
i
f
[
j
]
(
j
−
1
)
!
×
2
C
i
−
j
2
(
i
−
j
)
!
=
2
C
i
2
(
i
−
1
)
!
\sum_{j=1}^i \frac{f[j]}{(j-1)!}\times \frac{2^{C_{i-j}^2}}{(i-j)!}=\frac{2^{C_i^2}}{(i-1)!}
∑j=1i(j−1)!f[j]×(i−j)!2Ci−j2=(i−1)!2Ci2
看成一个形如 f ( i ) = ∑ j = 1 n f ( i − j ) × g ( j ) f(i)=\sum_{j=1}^nf(i-j)\times g(j) f(i)=∑j=1nf(i−j)×g(j)的一个式子,就可以用分治 F F T FFT FFT了
其实分治 F F T FFT FFT就是考虑算出来的对未算的贡献,满足这个样子的都可以这么算
复杂度 n l o g 2 n nlog^2n nlog2n,常数不优秀可能会被卡,实测比第一种方法慢一倍,多项式求逆不卡常是 9 s 9s 9s多,下面的代码是 17 s 17s 17s多
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define maxn 600005
#define LL long long
using namespace std;
int n,a[maxn],b[maxn],f[maxn],rev[maxn],g[maxn];
int fac[maxn],inv[maxn];
const int mod=1004535809;
inline int qpow(int x,int k){
int ret=1;
while(k){
if(k&1) ret=1LL*ret*x%mod; x=1LL*x*x%mod;
k>>=1;
} return ret%mod;
}
inline void NTT(int *F,int type,int limit){
for(int i=0;i<limit;i++)
if(i<rev[i]) swap(F[i],F[rev[i]]);
for(int mid=1;mid<limit;mid<<=1){
int Wn=qpow(3,type==1?(mod-1)/(mid<<1):(mod-1-(mod-1)/(mid<<1)));
for(int r=mid<<1,j=0;j<limit;j+=r){
int w=1;
for(int k=0;k<mid;k++,w=1LL*w*Wn%mod){
int x=F[j+k],y=1LL*w*F[j+k+mid]%mod;
F[j+k]=(x+y)%mod,F[j+mid+k]=(x-y+mod)%mod;
}
}
}
if(type==-1){
int Inv=qpow(limit,mod-2);
for(int i=0;i<limit;i++) F[i]=1LL*F[i]*Inv%mod;
}
}
inline void cdqFFT(int l,int r){
if(l==r) return;
int mid=(l+r)>>1,len=r-l-1;
cdqFFT(l,mid); int limit=1,lim=0;
while(limit<=len) limit<<=1,++lim;
for(int i=0;i<limit;i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(lim-1));
memset(a,0,sizeof(int)*limit); memset(b,0,sizeof(int)*limit);
for(int i=l;i<=mid;i++) a[i-l]=f[i];
for(int i=1;i<=r-l;i++) b[i-1]=g[i];
NTT(a,1,limit);NTT(b,1,limit);
for(int i=0;i<limit;i++) a[i]=1LL*a[i]*b[i]%mod;
NTT(a,-1,limit);
for(int i=mid+1;i<=r;i++) f[i]=(f[i]-a[i-l-1]+mod)%mod;
cdqFFT(mid+1,r);
}
int main(){
scanf("%d",&n); fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1LL*fac[i-1]*i%mod;
inv[n]=qpow(fac[n],mod-2);
for(int i=n;i;i--) inv[i-1]=1LL*inv[i]*i%mod;
for(int i=1;i<=n;i++) f[i]=1LL*qpow(2,(1LL*i*(i-1)>>1)%(mod-1))*inv[i-1]%mod;
for(int i=0;i<=n;i++) g[i]=1LL*qpow(2,(1LL*i*(i-1)>>1)%(mod-1))*inv[i]%mod;
cdqFFT(1,n);
printf("%lld\n",1LL*f[n]*fac[n-1]%mod);
return 0;
}
第三种是指数生成函数
还没学
e
x
p
exp
exp
留坑待填