LOJ#6538. 烷基计数 加强版 加强版/LG P6598 烷烃计数

Description

$n$ 个碳原子的烷烃共有多少种同分异构体?

提示:如果你不知道什么是烷烃,那么你可以认为这个问题等价于求 $n$ 个点的无标号无根树并满足每个点的度数 $\leq 4$ 的树的个数。

Solution

先对烷基计数,即求$n$个碳原子的烷基共有多少种

先忽视烷基中单独的那个电子,那么烷基可以视为三叉树,如果有不饱和的C,可以看做其有一个大小为0的子树

根据burnside引理,设$n$个C的烷基有$f_n$个,那么:

$$f_n=\frac{\sum _{i+j+k=n-1}f_if_jf_k+3\sum _{2i+j=n-1}f_if_j+2\sum _{3i=n-1}f_i}{6} $$

设$A(x)$是其生成函数,那么:

$$A(x)=\frac{A^3(x)+3A(x^2)A(x)+2A(x^3)}{6}x+1 $$

递推$A$可以用分治FFT或者牛顿迭代解决

再尝试证明结论:

在一个已知的烷烃(一个无根树)中,设$p$为将一个烷烃中的C特殊标记后,有多少不同的烷烃,$q$为将一个烷烃中的碳碳键特殊标记后,有多少不同的烷烃,$b$为该烷烃是否有两个重心且两重心两侧同构的布尔变量,那么:

$$p-q+b=1$$

分类讨论,当$b=false$时,任选一个重心,该重心被标记后必不会与其它点标记的情况同构,如果有两个C被标记后同构,那么这两个C向父亲方向(以重心为根)的碳碳键被标记后一定同构

当$b=true$时,除重心外的C同上,两个重心只产生一个同构,两重心之间连边产生一个同构,所以$p=q$

那么枚举所有的无根树,就有$\sum p-\sum q+\sum b=\sum 1$,其实题中就是求$\sum 1$

所以现在要计算$\sum p$,$\sum q$和$\sum b$

计算$\sum p$:

  就是计算有根树的个数(将被标记的C看作根),用与计算烷基相似的方法计算有根树的个数,最终得到OGF$P(x)$为:

·  $$P(x)=\frac{A^4(x)+6A(x^2)A^2(x)+3A^2(x^2)+8A(x^3)A(x)+6A(x^4)}{24}x+1 $$

计算$\sum q$:

  可以看做让两个烷基之间成键,最终得到OGF$Q(x)$为:

  $$Q(x)=\frac{(A^2(x)-1)^2+A(x^2)-1}{2}+1 =\frac{A^2(x)+A(x^2)-2A^2(x)}{2}+1 $$

  在$A(x)$平方时将每个情况计算了两次,但是两个烷基相同的情况只计算了一次,所以写成这样

计算$\sum b$

  发现就是将两个相同的烷基之间成键,OGF为$A(x^2)$

答案OGF为$P(x)-Q(x)+A(x^2)$

#include<iostream>
#include<cstdio>
using namespace std;
int s,tot,rev[800005],n,T;
const int mod=998244353;
long long A[800005],inv[800005],A2[800005],A3[800005],ans[800005],C[800005];
inline long long read(){
    long long f=1,w=0;
    char ch=0;
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')w=(w<<1)+(w<<3)+ch-'0',ch=getchar();
    return f*w;
}
long long ksm(long long a,long long p){
    long long ret=1ll;
    while(p){
        if(p&1)(ret*=a)%=mod;
        (a*=a)%=mod,p>>=1;
    }
    return ret;
}
void ntt(long long *a,int N,int in){
    for(int i=0;i<N;i++)if(i<rev[i])swap(a[i],a[rev[i]]);
    for(int i=1;i<N;i<<=1){
        long long wn=ksm(3,(mod-1)/i/2);
        if(in==-1)wn=ksm(wn,mod-2);
        for(int j=0;j<N;j+=i<<1){
            long long w=1;
            for(int k=j;k<i+j;k++){
                long long x=a[k],y=w*a[k+i]%mod;
                a[k]=(x+y)%mod,a[k+i]=(mod+x-y)%mod,(w*=wn)%=mod;
            }
        }
    }
    if(in==-1)for(int i=0;i<N;i++)(a[i]*=inv[N])%=mod;
}
void solve(int l,int r){
    if(l==r){(A[l]+=(l%3==1)*A[l/3]*inv[3]%mod)%=mod;return;}
    int mid=(l+r)>>1;
    solve(l,mid),s=2,tot=1;
    static long long a1[800005],a2[800005],b[800005],c[800005];
    while(s<=(2*(r-l+1)))s<<=1,++tot;
    for(int i=0;i<s;i++)a1[i]=i<=mid-l?A[l+i]:0,a2[i]=i<=r-l?A[i]:0,b[i]=(i<=r-l&&!(i&1))?A[i/2]:0;
    for(int i=0;i<s;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(tot-1));
    ntt(a1,s,1),ntt(a2,s,1),ntt(b,s,1);
    for(int i=0;i<s;i++)c[i]=(a1[i]*a2[i]%mod*a2[i]%mod*(l?inv[2]:inv[6])%mod+a1[i]*b[i]%mod*inv[2]%mod)%mod;
    ntt(c,s,-1);
    for(int i=mid-l;i<=r-l-1;i++)(A[l+i+1]+=c[i])%=mod;
    solve(mid+1,r);
}
int main(){
    inv[0]=inv[1]=1,T=read();
    for(int i=2;i<=800000;i++)inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    A[0]=1,solve(0,100000),s=2,tot=1;
    while(s<=300000)s<<=1,++tot;
    for(int i=0;i<=100000;i++)A2[i]=i&1?0:A[i/2],A3[i]=i%3?0:A[i/3];
    for(int i=0;i<=100000;i++)if(i%4==1)ans[i]=A[i/4]*inv[4]%mod;
    for(int i=0;i<=100000;i++)if(!(i&1))ans[i]=A[i/2];
    for(int i=0;i<s;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(tot-1));
    ntt(A,s,1),ntt(A2,s,1),ntt(A3,s,1);
    for(int i=0;i<s;i++)C[i]=(A2[i]*A2[i]%mod*inv[8]%mod+A[i]*A[i]%mod*A2[i]%mod*inv[4]%mod+A[i]*A3[i]%mod*inv[3]%mod+A[i]*A[i]%mod*A[i]%mod*A[i]%mod*inv[24]%mod)%mod;
    ntt(C,s,-1);
    for(int i=0;i<=100000;i++)(ans[i+1]+=C[i])%=mod;
    for(int i=0;i<s;i++)C[i]=(A[i]*A[i]%mod*inv[2]%mod+A2[i]*inv[2]%mod-A[i]+mod)%mod;
    ntt(C,s,-1);
    for(int i=0;i<=100000;i++)(ans[i]+=mod-C[i])%=mod;
    for(;T;T--)n=read(),printf("%lld\n",ans[n]);
    return 0;
}
烷烃计数

 

posted @ 2021-03-04 22:21  QDK_Storm  阅读(277)  评论(0编辑  收藏  举报