烷烴計數(Burnside引理,分治FFT)

在本學期的期末考試中, 連原電池正負極都分不清的你深感自己要爆零了沒錯正是在下. 就在這時, 你看到了一道附加題:
33.\texttt{33.}(本題共計 100\texttt{100} 分)23333\texttt{23333} 烷的同分異構體個數爲 _______。(不考慮立體異構)
是時候表演真正的計數了! 現在, 請你快速計算化學式爲 CnH2n+2C_nH_{2n+2} 的烷烴的同分異構體個數.
多組數據. 答案對 998244353998244353 取模.

OIOI語來說就是求每個點度數4\leq 4的無標號無根樹的個數。
有一個經典的DPDP做法:
考慮無標號有根樹的計數:
fi,jf_{i,j}表示根有jj個兒子,總共有ii個點的合法方案數。
在無標號的情況下,根相當於是一個特殊的節點。(用有機化學的角度可能對高中生更爲友好:有一個碳原子被特殊標記了)
那麼兩個無標號有根樹同構,當且僅當一棵樹根節點的所有兒子經過某種順序的置換後跟另一棵樹的所有兒子一一同構。
所以可以寫出轉移方程:
fi,j=1pjfipv,jp(p1+0k3fv,kp)f_{i,j} = \sum_{1\leq p \leq j} f_{i-pv,j-p} * \binom{p-1+\sum_{0\leq k \leq 3}f_{v,k}}{p}
vv代表新加入的子樹的大小,
pp代表要加入幾個。
顯然vv相同的子樹之間纔可能有同構關係,
後面那個組合數就是求vv相同的子樹選pp個可重的方案。

解決有根樹之後考慮無根樹:
如果只有一個重心,那麼以這個重心爲根的有根樹數量就是無根樹數量。
所以我們求一個每個兒子都2sz<n2 * sz\lt n的無標號有根樹方案即可,
再加上有兩個重心也就是兩個sz=n2sz = \frac n2的有根樹拼起來的方案數。
這樣我們就得到了一個O(n2)O(n^2)做法。

再考慮有根樹的DPDP
可以套用BurnsideBurnside引理,求根的33個兒子的置換後的不動點總數÷\div總置換數(爲了方便我們將少於33個兒子看作有33個兒子但是有兒子沒有包含碳原子):
fif_{i}表示樹的大小爲ii的方案數。

fn=i+j+k+1=nfifjfk+32i+j+1=nfifj+23i+1=nfi6f_n = \frac {\sum_{i+j+k+1=n}f_if_jf_k+3\sum_{2*i+j+1=n}f_if_j+2\sum_{3*i+1=n} f_i}6
A(x)=fixiA(x) = \sum f_ix^i
那麼有A(x)=xA(x)3+3A(x2)A(x)+2A(x3)6+1A(x) = x\frac{A(x)^3+3A(x^2)A(x)+2A(x^3)}6+1
這個可以使用牛頓迭代法或分治FFTFFT解決,
但是分治FFTFFT在代碼長度和常數上十分優秀兩個log跑1e5剛好1s
這個分治FFTFFT並沒有想象中那麼長(也就和多項式取模差不多長)。主要就是對於Solve(l,m,r)Solve(l,m,r)
我們求lpmA(x)[p]l\leq p\leq m,A(x)[p]對於A(x)3+3A(x2)A(x)A(x)^3+3A(x^2)A(x)[m+1,r][m+1,r]中的貢獻。
可以發現如果取m=l+r2m = \lfloor \frac {l+r}2 \rfloor的話,
l>0l>0的時候答案需要×3\times 3
否則不需要。
具體看代碼。

如何解決無根樹?
發現2sz<n2sz < n這個條件博主並不能套用進burnsideburnside這一套。

定理:
pp爲一顆無根樹的點等價類數(#分別#把所有nn個點當成特殊碳原子之後得到的nn個烷烴,求有多少種不同構的烷烴。)
qq爲一顆無根樹的邊等價類數(#分別#把所有n1n-1條邊當成特殊碳碳鍵之後得到的n1n-1個烷烴,求有多少種不同構的烷烴。)
ss爲這棵樹是否有兩個重心(有就是1,否則就是0).
那麼pq+s=1p-q+s=1
在這裏插入圖片描述
接下來我們來活用一下burnsideburnside引理:求所有不同構的無標號無根樹的pp之和。
這就是求不同構的無標號有根樹個數。
其生成函數用44個兒子的burnsideburnside引理
P(x)=x6A(x4)+6A(x2)A(x)2+3A(x2)2+8A(x3)A(x)+A(x)424P(x) = x\frac {6A(x^4)+6A(x^2)A(x)^2+3A(x^2)^2+8A(x^3)A(x)+A(x)^4}{24}

求所有不同構的無標號無根樹的qq之和。
就是求兩個無標號有根樹的根連起來後的不同構方案數。
其生成函數
Q(x)=(A(x)1)2+A(x2)12Q(x) = \frac {(A(x)-1)^2+A(x^2)-1}2

求所有不同構的無標號無根樹的ss之和。
其生成函數就是A(x2)A(x^2)

那麼,
C(x)=P(x)Q(x)+A(x2)C(x) = P(x) - Q(x) + A(x^2)
就是烷烴數量的生成函數。
因爲對於每個等價類都被pq+sp-q+s不重不漏的計算了。

代碼:

#include<bits/stdc++.h>
#define maxn 900005
#define mod 998244353
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int Wl,Wl2,w[maxn],lg[maxn],inv[maxn],fac[maxn],invf[maxn],r[maxn];
int Pow(int b,int k){ int r=1;for(;k;k>>=1,b=1ll*b*b%mod)if(k&1) r=1ll*r*b%mod;return r; }
void init(int n){
	for(Wl=w[0]=inv[0]=inv[1]=fac[0]=fac[1]=invf[0]=invf[1]=1;n>=Wl<<1;Wl<<=1);
	w[1]=Pow(3,(mod-1)/(Wl2=Wl<<1));
	rep(i,2,Wl2) w[i]=1ll*w[i-1]*w[1]%mod,fac[i]=1ll*fac[i-1]*i%mod,lg[i]=lg[i>>1]+1,
		inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod,invf[i]=1ll*invf[i-1]*inv[i]%mod;
}
void NTT(int *A,int n,int tp){
	rep(i,1,n-1) i<(r[i]=r[i>>1]>>1|(i&1)<<lg[n]-1) && (swap(A[i],A[r[i]]),0);
	for(int L=1,B=Wl;L<n;L<<=1,B>>=1) for(int s=0;s<n;s+=L<<1) for(int k=s,x=0,t;k<s+L;k++,x+=B)
		t=1ll*w[tp^1?Wl2-x:x]*A[k+L]%mod,A[k+L]=(A[k]-t)%mod,A[k]=(A[k]+t)%mod;
	if(tp^1) rep(i,0,n-1) A[i]=1ll*A[i]*inv[n]%mod;
}

int A[maxn],A2[maxn],A3[maxn],ans[maxn],n;
void Solve(int l,int r){
	if(l==r) return (void)(A[l] = (A[l] + (l % 3 == 1) * (A[l / 3] * 1ll * inv[3])) % mod);
	int m=l+r>>1;
	Solve(l,m);
	static int a1[maxn],a2[maxn],b[maxn],c[maxn];
	int L = 1 << lg[2*r+m-3*l] + 1;
	rep(i,0,L-1) 
		a1[i] = i<=m-l?A[l+i]:0,
		a2[i] = i<=r-l?A[i]:0 , 
		b[i] = i<=r-l?(i&1?0:A[i/2]):0;
	NTT(a1,L,1),NTT(a2,L,1),NTT(b,L,1);
	rep(i,0,L-1) c[i] = (1ll * a1[i] * a2[i] % mod * a2[i] % mod * (l ? inv[2] : inv[6]) + 1ll * a1[i] * b[i] % mod * inv[2]) % mod;
	NTT(c,L,-1);
	rep(i,m-l,r-l-1) A[i+l+1] = (A[i+l+1] + c[i]) % mod;
	Solve(m+1,r);
}

int main(){
	
	freopen("alkane.in","r",stdin);
	freopen("alkane.out","w",stdout);
	
	init(max(10,300000));
	A[0]=1;
	Solve(0,100000);
	
	rep(i,0,Wl2-1) A2[i] = (i&1?0:A[i/2]) , A3[i] = (i%3?0:A[i/3]);
	rep(i,0,25000) ans[4*i+1] = 1ll * inv[4] * A[i] % mod;
	rep(i,0,50000) ans[2*i] = A[i];
	static int t[maxn]={}; 
	NTT(A,Wl2,1),NTT(A2,Wl2,1),NTT(A3,Wl2,1);
	rep(i,0,Wl2-1) t[i] = (1ll * A2[i] * A2[i] % mod * inv[8] + 1ll * A[i] * A[i] % mod * A2[i] % mod * inv[4] 
		+ 1ll * A[i] * A3[i] % mod * inv[3] + 1ll * A[i] * A[i] % mod * A[i] % mod * A[i] % mod  * inv[24]) % mod;
	NTT(t,Wl2,-1);
	rep(i,0,100000) ans[i+1] = (ans[i+1] + t[i]) % mod;
	rep(i,0,Wl2-1) t[i] = (1ll * A[i] * A[i] % mod * inv[2] + A2[i] * 1ll * inv[2] - A[i]) % mod;
	NTT(t,Wl2,-1);
	rep(i,0,100000) ans[i] = (ans[i] - t[i]) % mod;
	
	int T;scanf("%d",&T);
	for(;T--;){
		int n;scanf("%d",&n);
		printf("%d\n",(ans[n]+mod)%mod);
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章