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

8 篇文章 0 订阅
8 篇文章 0 订阅

这里写图片描述

分析:
样例解释:

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

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

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

B(x)=i=0Ai(x)=11A(x) B ( x ) = ∑ i = 0 A i ( x ) = 1 1 − A ( x )

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


多项式求逆

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

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

求解过程

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

A(x)B(x)1(modxn)(1) A ( x ) B ( x ) ≡ 1 ( m o d · x n ) ( 1 )

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

A(x)B'(x)1(modxn2)(2) A ( x ) B ′ ( x ) ≡ 1 ( m o d · x ⌈ n 2 ⌉ ) ( 2 )

再将 (1) ( 1 ) 放到mod xn2 x ⌈ n 2 ⌉ 意义下

A(x)B(x)1(modxn2)(3) A ( x ) B ( x ) ≡ 1 ( m o d · x ⌈ n 2 ⌉ ) ( 3 )

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

B(x)B'(x)0(modxn2) B ( x ) − B ′ ( x ) ≡ 0 ( m o d · x ⌈ n 2 ⌉ )

两边平方

B2(x)2B'(x)B(x)+B'2(x)0(modxn) B 2 ( x ) − 2 B ′ ( x ) B ( x ) + B ′ 2 ( x ) ≡ 0 ( m o d · x n )

这里解释一下平方后为什么模的 xn2 x ⌈ n 2 ⌉ 也会平方
左边多项式在 modxn m o d · x n 意义下为 0 0 ,那么就说明其0 n1 n − 1 次项系数都为 0 0
平方了之后,对于第0i2n1项,其系数 ai a i ij=0ajaij ∑ j = 0 i a j a i − j ,很明显 j j ij之间必然有一个值小于 n n ,因此ai必然是 0 0 ,也就是说平方后在mod·x2n意义下仍然为 0 0

然后同时乘上A(x) A(x)B(x)1(modxn) A ( x ) B ( x ) ≡ 1 ( m o d · x n ) ),移项可以得到

B(x)2B'(x)A(x)B'2(x)(modxn) B ( x ) ≡ 2 B ′ ( x ) − A ( x ) B ′ 2 ( x ) ( m o d · x n )
B(x)B'(x)(2A(x)B'(x))(modxn) B ( x ) ≡ B ′ ( x ) ( 2 − A ( x ) B ′ ( x ) ) ( m o d · x n )

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

T(n)=T(n2)+O(nlogn)=O(nlogn) T ( n ) = T ( n 2 ) + O ( n l o g n ) = O ( n l o g n )

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


声明一下,1005060097的原根是5

在FFT中,有原根 wn=e2πin=(cos(2πn),sin(2πn)i) w n = e 2 π i n = ( c o s ( 2 π n ) , s i n ( 2 π n ) i )
NTT中,我们设 G G P的原根,会发现 GP1n G P − 1 n 具有和 wn=e2πin w n = e 2 π i n 相似的性质

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

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) ( r ∗ 2 k + 1 ) 原根表

r2k+1 r ∗ 2 k + 1 rkg
3112
5122
17143
97355
193365
257183
768115917
1228931211
409615133
655371163
78643331810
576716911193
73400337203
2306867311213
10485760125223
1677721615253
4697620497263
998244353119233
1004535809479213
2013265921152731
228170137717273
32212254733305


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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值