HDU 6426 Problem A.Alkane(生成函数+NTT+polya)

20 篇文章 0 订阅
13 篇文章 0 订阅

Description

求烷烃 CnH2n+2 C n H 2 n + 2 和烷基 CnH2n+1 C n H 2 n + 1 的同分异构体个数

Input

第一行一整数 T T 表示用例组数,每组用例输入一整数n(1T,n105)

Output

输出烷烃和烷基的同分异构体个数

Sample Input

9
1
2
3
4
5
6
7
8
9

Sample Output

1 1
1 1
1 2
2 4
3 8
5 17
9 39
18 89
35 211

Solution

先考虑求烷基个数,设其生成函数为 A(x) A ( x ) ,也即求有 n n 个节点、每个节点出度不超过3的不同构有根树个数,由 ploya p l o y a ,根节点的三棵子树(可以是空树)之间的旋转变换群有 3!=6 3 ! = 6 个,考虑在每种变换群下的保持不变的方案数,对于恒同变换,对三棵子树没有要求,方案为 A3(x) A 3 ( x ) ;对于一对置换,要求其中两棵子树相同,另一棵子树没有要求,方案为 3A(x)A(x2) 3 A ( x ) A ( x 2 ) ;对于三轮换,要求三棵子树相同,方案为 A(x3) A ( x 3 ) ,故有

A(x)=1+xA3(x)+3A(x)A(x2)+2A(x3)6 A ( x ) = 1 + x ⋅ A 3 ( x ) + 3 A ( x ) A ( x 2 ) + 2 A ( x 3 ) 6

CDQ C D Q 分治+ NTT N T T 即可求出 A(x) A ( x ) ,其中需要注意的是,当我们考虑区间 [l,mid) [ l , m i d ) 对区间 [mid,r) [ m i d , r ) 的影响时, A(x3) A ( x 3 ) 部分直接统计, A(x)A(x2) A ( x ) A ( x 2 ) 部分用 [Al,...,Amid1] [ A l , . . . , A m i d − 1 ] A(x2) A ( x 2 ) 做卷积即可,而 A3(x) A 3 ( x ) 部分,注意到只有 a+b+c[mid1,r1) a + b + c ∈ [ m i d − 1 , r − 1 ) a,b,c a , b , c 中至少有一个介于 [l,mid) [ l , m i d ) 时才会对 Aa+b+c+1 A a + b + c + 1 有贡献 AaAbAc A a A b A c ,若 l=0 l = 0 ,则直接对 A0,...,Amid1 A 0 , . . . , A m i d − 1 自身做三遍卷积即可,若 l>0 l > 0 ,考虑 a[l,mid) a ∈ [ l , m i d ) 的贡献,此时 b,c<rl b , c < r − l ,而 A0,...,Arl A 0 , . . . , A r − l 必然已经求出,故用 Al,...,Amid1 A l , . . . , A m i d − 1 A0,...,Arl A 0 , . . . , A r − l 以及 A0,...,Arl A 0 , . . . , A r − l 做卷积即可

之后考虑求烷烃个数,设其生成函数为 B(x) B ( x ) ,对于一棵 n n 个节点无根树,设其点等价类有c个,边等价类有 d d 个(两个点等价当且仅当以这两个点为根得到的有根树同构,两条边等价当且仅当分别在这两条边中插入一个点,以插入点为根得到的有根树同构),对称边有e条(断掉这条边得到的两棵子树同构),由于 e1 e ≤ 1 ,分别考虑两种情况

1. e=0 e = 0 ,即没有对称边,此时任选一个重心为根,显然重心这个点成为一个点等价类(否则与其等价的点必然也是重心,两个重心之间必然有边,这条边即为对称边,与 e=0 e = 0 矛盾),之后对于每个点等价类,其中每个元素与其父亲连的边必然也形成一个边等价类,反之同理,故 cd=1 c − d = 1

2. e=1 e = 1 ,即存在一条对称边,在这条边中间插入一个点作为根,同理除根节点外,每个点等价类对应一个边等价类,故有 cd=0 c − d = 0

综上有,对任意一棵无根树, cd+e=1 c − d + e = 1

注意到一个烷烃即为一个 n n 个节点、每点度数不超过4的无根树,对所有不同构的烷烃求和该式有

cd+e=1 ∑ c − ∑ d + ∑ e = ∑ 1

右端即为所求,分别求出烷烃中 c,d,e ∑ c , ∑ d , ∑ e 的生成函数 C(x),D(x),E(x) C ( x ) , D ( x ) , E ( x ) 即可

首先考虑 E(x) E ( x ) ,显然 En0 E n ≠ 0 n n 必然为偶数,把树从对称边切开即得到两棵点数为n2、每个节点出度不超过 3 3 的有根树,进而有E(x)=A(x2)

对于 C(x) C ( x ) ,注意到只需任选一个点做根得到的不同构有根树数量即为 Cn C n ,同理由 polya p o l y a ,四棵子树的旋转变换群有 4!=24 4 ! = 24 种,其中恒同变换对四棵子树没有限制,方案为 A4(x) A 4 ( x ) 3 3 种双对换构成的变换要求两对子树相同,方案为3A2(x2) 6 6 种一对换构成的变换要求一对子树相同,另外两棵子树没有限制,方案为6A(x2)A2(x) 8 8 种三轮换构成的变换要求三棵子树相同,另外一棵子树没有限制,方案为8A(x)A(x3) 6 6 种四轮换构成的变换要求四棵子树相同,方案为6A(x4),故有

C(x)=xA4(x)+3A2(x2)+6A(x2)A2(x)+8A(x)A(x3)+6A(x4)24 C ( x ) = x ⋅ A 4 ( x ) + 3 A 2 ( x 2 ) + 6 A ( x 2 ) A 2 ( x ) + 8 A ( x ) A ( x 3 ) + 6 A ( x 4 ) 24

而对于 D(x) D ( x ) ,注意到只需任选一条边拆开加点,以新点为根的不同构有根树数量即为 Dn D n ,此时要求两棵子树非空,由 polya p o l y a ,恒同变换对两个子树没有限制,方案为 (A(x)1)2 ( A ( x ) − 1 ) 2 ,对换要求两棵子树相同,方案为 A(x2)1 A ( x 2 ) − 1 ,故有
D(x)=(A(x)1)2+(A(x2)1)2 D ( x ) = ( A ( x ) − 1 ) 2 + ( A ( x 2 ) − 1 ) 2

故求出 A(x) A ( x ) 后即可得到 C(x),D(x),E(x) C ( x ) , D ( x ) , E ( x ) ,进而得到 B(x)=C(x)D(x)+A(x2) B ( x ) = C ( x ) − D ( x ) + A ( x 2 )

Code

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxlen=700000,mod=998244353,g=3;
int mul(int x,int y)
{
    ll z=1ll*x*y;
    return z-z/mod*mod;
}
int add(int x,int y)
{
    x+=y;
    if(x>=mod)x-=mod;
    return x;
} 
int Pow(int x,int y)
{
    int ans=1;
    while(y)
    {
        if(y&1)ans=mul(ans,x);
        x=mul(x,x);
        y>>=1;
    }
    return ans;
}
int wn[maxlen],pos[maxlen],inv;
void ntt_init(int len)
{
    int j=0;
    while((1<<j)<len)j++;
    j--;
    for(int i=0;i<len;i++)
        pos[i]=pos[i>>1]>>1|((i&1)<<j);
    inv=Pow(len,mod-2);
    int x=Pow(g,(mod-1)/len);
    wn[0]=1;
    for(int i=1;i<=len;i++)wn[i]=mul(x,wn[i-1]);
}
void ntt(int *a,int len,int sta) 
{
    for(int i=0;i<len;i++)
        if(i<pos[i])swap(a[i],a[pos[i]]);
    for(int k=1;k<len;k<<=1) 
        for(int i=0;i<len;i+=k<<1) 
        {
            int t=len/(k<<1);
            for(int j=0;j<k;j++) 
            {
                int w=sta==1?wn[t*j]:wn[len-t*j];
                int x=a[i+j],y=mul(w,a[i+j+k]);
                a[i+j]=add(x,y);a[i+j+k]=add(x,mod-y);
            }
        }
    if(sta==-1)
        for(int i=0;i<len;i++)a[i]=mul(a[i],inv);
}
int A[maxlen],A1[maxlen],A2[maxlen],A3[maxlen],A4[maxlen],B[maxlen],C[maxlen],D[maxlen];
int i2,i3,i6,i24;
void Solve(int l,int r)
{
    if(l+1==r)return ;
    int mid=(l+r)/2,len=(r-l)<<1;
    Solve(l,mid);
    ntt_init(len);
    for(int i=l;i<mid;i++)C[i-l]=A[i];
    for(int i=0;i<r-l;i++)
    {
        A1[i]=A[i];
        if(2*i<r-l)A2[2*i]=A[i];
    }
    for(int i=l;3*i+1<r;i++)
        if(3*i+1>=mid)A[3*i+1]=add(A[3*i+1],mul(A[i],i3));
    ntt(C,len,1),ntt(A1,len,1),ntt(A2,len,1);
    for(int i=0;i<len;i++)D[i]=mul(C[i],A2[i]);
    ntt(D,len,-1);
    for(int i=mid;i<r;i++)A[i]=add(A[i],mul(D[i-l-1],i2));
    for(int i=0;i<len;i++)D[i]=mul(C[i],mul(A1[i],A1[i]));
    ntt(D,len,-1);
    if(l)
        for(int i=mid;i<r;i++)A[i]=add(A[i],mul(D[i-l-1],i2));
    else
        for(int i=mid;i<r;i++)A[i]=add(A[i],mul(D[i-l-1],i6));
    for(int i=0;i<len;i++)C[i]=A1[i]=A2[i]=0;
    Solve(mid,r);
}
void Pre(int n)
{
    i2=Pow(2,mod-2),i3=Pow(3,mod-2),i6=Pow(6,mod-2),i24=Pow(24,mod-2);
    A[0]=1;
    Solve(0,n);
    for(int i=0;i<n;i++)A1[i]=A2[2*i]=A3[3*i]=A4[4*i]=A[i];
    int len=4*n;
    ntt_init(len);
    ntt(A1,len,1),ntt(A2,len,1),ntt(A3,len,1),ntt(A4,len,1);
    for(int i=0;i<len;i++)
    {
        C[i]=mul(mul(A1[i],A1[i]),mul(A1[i],A1[i]));
        C[i]=add(C[i],mul(3,mul(A2[i],A2[i])));
        C[i]=add(C[i],mul(6,mul(mul(A1[i],A1[i]),A2[i])));
        C[i]=add(C[i],mul(8,mul(A1[i],A3[i])));
        C[i]=add(C[i],mul(6,A4[i]));
        C[i]=mul(C[i],i24);
    }
    ntt(C,len,-1);
    for(int i=n-1;i;i--)C[i]=C[i-1];
    C[0]=0;
    for(int i=0;i<len;i++)
    {
        int x=add(A1[i],mod-1),y=add(A2[i],mod-1);
        D[i]=mul(i2,add(mul(x,x),y));
    }
    ntt(D,len,-1);
    for(int i=0;i<n;i++)
    {
        B[i]=add(C[i],mod-D[i]);
        if(i%2==0)B[i]=add(B[i],A[i>>1]);
    }
}
int main()
{
    Pre(1<<17);
    int T,n;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        printf("%d %d\n",B[n],A[n]);
    }
    return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值