在本學期的期末考試中, 連原電池正負極都分不清的你深感自己要爆零了沒錯正是在下. 就在這時, 你看到了一道附加題:
(本題共計 分) 烷的同分異構體個數爲 _______。(不考慮立體異構)
是時候表演真正的計數了! 現在, 請你快速計算化學式爲 的烷烴的同分異構體個數.
多組數據. 答案對 取模.
用語來說就是求每個點度數的無標號無根樹的個數。
有一個經典的做法:
考慮無標號有根樹的計數:
表示根有個兒子,總共有個點的合法方案數。
在無標號的情況下,根相當於是一個特殊的節點。(用有機化學的角度可能對高中生更爲友好:有一個碳原子被特殊標記了)
那麼兩個無標號有根樹同構,當且僅當一棵樹根節點的所有兒子經過某種順序的置換後跟另一棵樹的所有兒子一一同構。
所以可以寫出轉移方程:
代表新加入的子樹的大小,
代表要加入幾個。
顯然相同的子樹之間纔可能有同構關係,
後面那個組合數就是求相同的子樹選個可重的方案。
解決有根樹之後考慮無根樹:
如果只有一個重心,那麼以這個重心爲根的有根樹數量就是無根樹數量。
所以我們求一個每個兒子都的無標號有根樹方案即可,
再加上有兩個重心也就是兩個的有根樹拼起來的方案數。
這樣我們就得到了一個做法。
再考慮有根樹的
可以套用引理,求根的個兒子的置換後的不動點總數總置換數(爲了方便我們將少於個兒子看作有個兒子但是有兒子沒有包含碳原子):
表示樹的大小爲的方案數。
設
那麼有
這個可以使用牛頓迭代法或分治解決,
但是分治在代碼長度和常數上十分優秀兩個log跑1e5剛好1s。
這個分治並沒有想象中那麼長(也就和多項式取模差不多長)。主要就是對於
我們求對於在中的貢獻。
可以發現如果取的話,
在的時候答案需要
否則不需要。
具體看代碼。
如何解決無根樹?
發現這個條件博主並不能套用進這一套。
定理:
設爲一顆無根樹的點等價類數(#分別#把所有個點當成特殊碳原子之後得到的個烷烴,求有多少種不同構的烷烴。)
爲一顆無根樹的邊等價類數(#分別#把所有條邊當成特殊碳碳鍵之後得到的個烷烴,求有多少種不同構的烷烴。)
爲這棵樹是否有兩個重心(有就是1,否則就是0).
那麼。
接下來我們來活用一下引理:求所有不同構的無標號無根樹的之和。
這就是求不同構的無標號有根樹個數。
其生成函數用個兒子的引理
求所有不同構的無標號無根樹的之和。
就是求兩個無標號有根樹的根連起來後的不同構方案數。
其生成函數
求所有不同構的無標號無根樹的之和。
其生成函數就是。
那麼,
就是烷烴數量的生成函數。
因爲對於每個等價類都被不重不漏的計算了。
代碼:
#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);
}
}