cogs 2259. 异化多肽(生成函数+NTT+多项式求逆【原根)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wu_tongtong/article/details/79562897

这里写图片描述

分析:
样例解释:

2 2
1 1 1 1
1 1 2
1 2 1
2 1 1

一个类似组合问题,可以想到生成函数
那我们先构造一个生成函数吧:
A(x)表示氨基酸的生成函数,有一种质量为i的氨基酸,xi前的系数就+1

因为我们每种可以选任意个,也存在顺序问题,那么Ai(x)就是选择i个氨基酸的方案数
那么答案显然是A(x)+A2(x)+A3(x)+...+A(x)n次项系数

B(x)=i=0Ai(x)=11A(x)

那么我们只要对多项式1A(x)求逆,得到的多项式B(x)xn的系数就是答案


多项式求逆

对于一个多项式A(x),称其最高项的次数为这个多项式的(degree),记为degA

对于一个多项式A(x),如果存在B(x)满足degB<=degA,并且A(x)B(x)1 (mod xn)
那么称B(x)A(x)在mod xn意义下的逆元,记作A1(x)

求解过程

现在考虑如何求A1(x),当n=1时,A(x)c (mod x),c是一个常数,这样,A1(x)就是c1
对于n>1的情况,设B(x)=A1(x)由定义可以知道

A(x)B(x)1(mod·xn)(1)

假设在mod xn2意义下A(x)的逆元是B(x)并且我们已经求出,那么

A(x)B(x)1(mod·xn2)(2)

再将(1)放到mod xn2意义下

A(x)B(x)1(mod·xn2)(3)

然后(2)(3)就可以得到

B(x)B(x)0(mod·xn2)

两边平方

B2(x)2B(x)B(x)+B2(x)0(mod·xn)

这里解释一下平方后为什么模的xn2也会平方
左边多项式在mod·xn意义下为0,那么就说明其0n1次项系数都为0
平方了之后,对于第0i2n1项,其系数aij=0iajaij,很明显jij之间必然有一个值小于n,因此ai必然是0,也就是说平方后在mod·x2n意义下仍然为0

然后同时乘上A(x)A(x)B(x)1(mod·xn)),移项可以得到

B(x)2B(x)A(x)B2(x)(mod·xn)
B(x)B(x)(2A(x)B(x))(mod·xn)

这样就可以得到mod xn意义下的逆元了,利用FFT/NTT加速之后可以做到在O(nlogn)时间内解决当前问题,最后总的时间复杂度也就是

T(n)=T(n2)+O(nlogn)=O(nlogn)

顺便一提,由这个过程可以看出,一个多项式有没有逆元完全取决于其常数项是否有逆元


声明一下,1005060097的原根是5

在FFT中,有原根wn=e2πin=(cos(2πn),sin(2πn)i)
NTT中,我们设GP的原根,会发现GP1n具有和wn=e2πin相似的性质

目前求原根的方法就是暴力啊:

int cal(ll x) {
    if (x==2) return 1;
    //暴力判断g^(p-1)同余于1(mod p)是否当且仅当指数为p-1的时候成立
    for (int i=2;i;i++) {
        bool flag=1;
        for (ll j=2;j*j<x;j++)
            if (KSM(i,(x-1)/j)==1) {
            //g^((p-1)/n)
                flag=0;
                break;
            }
        if (flag) return i;
    } 
}

下面给出的(r2k+1)原根表

r2k+1 r k g
3 1 1 2
5 1 2 2
17 1 4 3
97 3 5 5
193 3 6 5
257 1 8 3
7681 15 9 17
12289 3 12 11
40961 5 13 3
65537 1 16 3
786433 3 18 10
5767169 11 19 3
7340033 7 20 3
23068673 11 21 3
104857601 25 22 3
167772161 5 25 3
469762049 7 26 3
998244353 119 23 3
1004535809 479 21 3
2013265921 15 27 31
2281701377 17 27 3
3221225473 3 30 5


tip

注意时刻取模,防止爆ll

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long

using namespace std;

const ll p=1005060097;
const ll o=5;           //原根 
const int N=300010;
int n,m,fn;
ll a[N],b[N],c[N]; 

ll KSM(ll a,ll b) {
    ll t=1;
    a%=p;
    while (b) {
        if (b&1) t=(t*a)%p;
        b>>=1;
        a=(a*a)%p;
    }
    return t%p;
}

void NTT(int n,ll *a,int opt) {
    int i,j=0,k;
    for (i=0;i<n;i++) {
        if (i>j) swap(a[i],a[j]);
        for (int l=n>>1;(j^=l)<l;l>>=1);
    }
    for (i=2;i<=n;i<<=1) {
        int m=i>>1;
        ll wn=KSM(o,(p-1)/i);
        for (j=0;j<n;j+=i) {
            ll w=1;
            for (k=0;k<m;k++,w=(w*wn)%p) {
                ll z=(a[j+k+m]*w)%p;
                a[j+m+k]=(a[j+k]-z+p)%p;
                a[j+k]=(a[j+k]+z)%p;
            }
        }
    }
    if (opt==-1) reverse(a+1,a+n);
}

void inv(int n,ll *a,ll *b,ll *c) {
    if (n==1) {
        b[0]=KSM(a[0],p-2);
        return;
    }
    inv(n>>1,a,b,c);
    int k=n<<1;
    for (int i=0;i<n;i++) c[i]=a[i];
    for (int i=n;i<k;i++) c[i]=0;
    NTT(k,c,1); NTT(k,b,1);
    for (int i=0;i<k;i++) b[i]=(2-c[i]*b[i]%p+p)%p*b[i]%p;
    NTT(k,b,-1);
    ll inv=KSM(k,p-2);
    for (int i=0;i<n;i++) b[i]=(b[i]*inv)%p;    // /fn
    for (int i=n;i<k;i++) b[i]=0;
}

int main() 
{
    scanf("%d%d",&n,&m);
    int mx=0;
    for (int i=1;i<=m;i++) {
        int x;
        scanf("%d",&x);
        if (x<=n) a[x]=a[x]-1+p;       //1-A[x]
        mx=max(mx,x);
    }
    a[0]=1;          
    fn=1;
    while (fn<=mx*2) fn<<=1;
    inv(fn,a,b,c);
    printf("%lld\n",b[n]%p);
    return 0;
}
阅读更多

没有更多推荐了,返回首页