参考资料
题意简述
求 个碳原子的烷烃的同分异构体数量,对 取模。
等价于求 个点、每个点度数 的无标号无根树的个数。
烷基计数
烷基(Alkyl)是烷烃去掉一个氢原子得到的基团。从断键处把分子拎起来,失去氢的碳作根,它还能连 个碳,其余每个碳有一个父亲、至多 个儿子。于是烷基就是每个节点至多 个儿子的无标号有根树。
设 , 为 个碳的烷基数。令 ,对应空烷基,也就是一个氢原子。
根下挂一个大小 的烷基可重集。 已含空元素,所以「恰好挂 个」等同于「挂 个非空烷基」。对 用 Burnside 引理(Burnside's Lemma),循环型 、、 各有 个置换:
这是关于 的方程,用 牛顿迭代(Newton's Iteration)解。精度从 倍增到 时,、 只用到更低次的系数,当成常数。设:
对 求形式导数:
代入 化简:
数论变换(NTT)配上多项式求逆, 即可求出 。
烷烃计数
烷烃是度数 的无标号无根树。无根树不好直接数,钦定重心为根,转成有根树再容斥。
对一棵无根树数三样东西:
- 每个点轮流作根,得到的本质不同有根树数,记为点等价类数 ;
- 每条边作根(两端各挂一棵有根树),得到的本质不同方案数,记为边等价类数 ;
- 树恰有两个重心、且两重心等价时 ,否则 。
那么任意一棵无根树都满足 。
时以重心为根,除重心外每个点唯一对应它的父亲边,两点等价当且仅当父亲边等价,这部分贡献两两抵消,只剩重心自身的 ,即 。 时两重心互相等价,, 照样成立。
对所有无根树求和,记 的生成函数为 。 就是无根树总数,于是答案是 。
是点等价类:以某点为根,根的度数 ,即根下挂一个大小 的烷基可重集。对 用 Burnside 引理,循环型 、、、、 各有 个置换:
是边等价类:一条边两端各挂一个烷基,两端无序,端点是碳所以烷基非空。对 用 Burnside 引理:
对应双重心:两端挂同一个烷基:
答案就是 。时间复杂度为 。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int mod=998244353;
const int g=3;
ll Pow(ll x,ll y)
{
x%=mod;
ll res=1;
while(y)
{
if(y&1)res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
const ll inv2=(mod+1)/2;
const ll inv3=Pow(3,mod-2);
const ll inv24=Pow(24,mod-2);
void ntt(vector<ll> &a,int type)
{
int n=a.size();
for(int i=1,j=0;i<n;i++)
{
int bit=n>>1;
for(;j&bit;bit>>=1)j^=bit;
j^=bit;
if(i<j)swap(a[i],a[j]);
}
for(int len=2;len<=n;len<<=1)
{
ll wn=Pow(type==1?g:Pow(g,mod-2),(mod-1)/len);
for(int i=0;i<n;i+=len)
{
ll w=1;
for(int j=0;j<(len>>1);j++)
{
ll u=a[i+j],v=a[i+j+(len>>1)]*w%mod;
a[i+j]=(u+v)%mod;
a[i+j+(len>>1)]=(u-v+mod)%mod;
w=w*wn%mod;
}
}
}
if(type==-1)
{
ll iv=Pow(n,mod-2);
for(int i=0;i<n;i++)a[i]=a[i]*iv%mod;
}
}
vector<ll> mul(vector<ll> a,vector<ll> b,int len)
{
int tot=a.size()+b.size()-1;
int n=1;
while(n<tot)n<<=1;
a.resize(n);
b.resize(n);
ntt(a,1);
ntt(b,1);
for(int i=0;i<n;i++)a[i]=a[i]*b[i]%mod;
ntt(a,-1);
a.resize(min(len,tot));
return a;
}
vector<ll> inv(vector<ll> a,int len)
{
vector<ll> b={Pow(a[0],mod-2)};
for(int k=2;k<(len<<1);k<<=1)
{
vector<ll> c(a.begin(),a.begin()+min((int)a.size(),k));
c.resize(k);
vector<ll> t=mul(mul(b,b,k),c,k);
b.resize(k);
for(int i=0;i<k;i++)b[i]=(2*b[i]%mod-t[i]+mod)%mod;
}
b.resize(len);
return b;
}
vector<ll> comp(const vector<ll> &a,int s,int len)
{
vector<ll> res(len,0);
for(int i=0;i<(int)a.size()&&(ll)i*s<len;i++)res[i*s]=a[i];
return res;
}
vector<ll> alkyl(int len)
{
vector<ll> A={1};
for(int k=2;k<(len<<1);k<<=1)
{
vector<ll> A2=comp(A,2,k),A3=comp(A,3,k);
vector<ll> a2=mul(A,A,k),a3=mul(a2,A,k);
vector<ll> num(k,0),den(k,0);
for(int i=0;i<k;i++)
{
num[i]=(a3[i]-A3[i]+mod)%mod*inv3%mod;
den[i]=(a2[i]+A2[i])%mod*inv2%mod;
}
for(int i=k-1;i>=1;i--){num[i]=num[i-1];den[i]=den[i-1];}
num[0]=den[0]=0;
for(int i=0;i<k;i++){num[i]=(mod-num[i])%mod;den[i]=(mod-den[i])%mod;}
num[0]=(num[0]+1)%mod;
den[0]=(den[0]+1)%mod;
A=mul(num,inv(den,k),k);
}
A.resize(len);
return A;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin>>n;
int m=n+1;
vector<ll> A=alkyl(m);
vector<ll> A2=comp(A,2,m),A3=comp(A,3,m),A4=comp(A,4,m);
vector<ll> a2=mul(A,A,m);
vector<ll> P=mul(a2,a2,m);
vector<ll> p2=mul(a2,A2,m),p3=mul(A2,A2,m),p4=mul(A,A3,m);
for(int i=0;i<m;i++)P[i]=(P[i]+6*p2[i]+3*p3[i]+8*p4[i]+6*A4[i])%mod*inv24%mod;
vector<ll> B=A;
B[0]=(B[0]-1+mod)%mod;
vector<ll> Q=mul(B,B,m);
for(int i=0;i<m;i++)Q[i]=(Q[i]+A2[i])%mod*inv2%mod;
ll ans=((P[n-1]-Q[n]+A2[n])%mod+mod)%mod;
cout<<ans<<'\n';
return 0;
}