多项式求逆/指数生成函数/分治FFT--bzoj3456: 城市规划

5 篇文章 0 订阅

传送门
题意就是求 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 1ji1,在联通块内的点有 C i − 1 j − 1 C_{i-1}^{j-1} Ci1j1种,其他不与 1 1 1相连的有 2 C i − j 2 2^{C_{i-j}^2} 2Cij2种连法,所以总的就是:
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]=2Ci2j=1i1f[j]×Ci1j1×2Cij2
两边同除 ( i − 1 ) ! (i-1)! (i1)!
神奇地变到了 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)!} (i1)!f[i]=(i1)!2Ci2j=1i1(j1)!f[j]×(ij)!2Cij2
这就长得很卷积了

然后移项合并,就变成: ∑ 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(j1)!f[j]×(ij)!2Cij2=(i1)!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(j1)!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(j1)!2Cj2xj

A ∗ B ≡ C   m o d   x n + 1 A*B\equiv C\ mod\ x^{n+1} ABC mod xn+1
那么 A ≡ C ∗ B − 1   m o d   x n + 1 A\equiv C*B^{-1}\ mod\ x^{n+1} ACB1 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(j1)!f[j]×(ij)!2Cij2=(i1)!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(ij)×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
留坑待填

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值