[COGS2259]异化多肽(NTT+多项式求逆+生成函数)

本文介绍了一种利用多项式求逆解决生物信息学中蛋白质链计数问题的方法。通过构建生成函数并使用快速傅里叶变换(NTT),实现了高效计算。文章详细解释了多项式求逆的过程及其在实际问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

我是超链接

题解:

首先一看这不是指数生成函数,对于每一个氨基酸整一个生成函数,x^(Ci的倍数)。
但我们发现这个东西没法化简&优化,复杂度过不去

换个思路,我们把所有的氨基酸搞成一个生成函数,x^k的系数为相对分子质量为k的氨基酸有多少个,设为A(x),答案的多项式为B(x),那么

B(x)=1+A(x)+A2(x)+...=11A(x) B ( x ) = 1 + A ( x ) + A 2 ( x ) + . . . = 1 1 − A ( x )

那么我们只需要求1-A(x)的逆就好了


多项式求逆?
一个【多项式的逆】定义为对于一个多项式 A(x) A ( x ) 其逆 A1(x) A − 1 ( x ) 设为 B(x) B ( x )
A(x)B(x)1(%xn) A ( x ) B ( x ) ≡ 1 ( % x n )

求法?
分类讨论
当n=1的时候, A(x)c(%x) A ( x ) ≡ c ( % x ) ,那么 A1(x)=c1 A − 1 ( x ) = c − 1

当n>1的时候,设 A(x)B(x)1(%xn) A ( x ) B ( x ) ≡ 1 ( % x n )

移项 A(x)B(x)10(%xn) A ( x ) B ( x ) − 1 ≡ 0 ( % x n )

两边平方 A2(x)B2(x)+12A(x)B(x)0(%x2n) A 2 ( x ) B 2 ( x ) + 1 − 2 A ( x ) B ( x ) ≡ 0 ( % x 2 n )

C(x)A(x)1(%x2n) C ( x ) A ( x ) ≡ 1 ( % x 2 n )

对刚才那个柿子两边乘 C(x) C ( x ) ,得 B2(x)A(x)+C(x)2B(x)0(%x2n) B 2 ( x ) A ( x ) + C ( x ) − 2 B ( x ) ≡ 0 ( % x 2 n )

移项 C(x)=B(x)(2B(x)A(x)) C ( x ) = B ( x ) ( 2 − B ( x ) A ( x ) )

可以发现C是下一层的B,那我们写的时候C是中转,A是固定的,每次我们要把A复制给C,然后直接处理B就好了

我们可以通过递归来求,时间复杂度T(n)=T(n/2)+nlogn=nlogn,多项式乘法用NTT

NTT的时候,有一个快速幂的原根,是模数的原根,而这个很大模数的原根是5

数据范围瞎给!

代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#define LL long long
using namespace std;
const int N=1400000;
const int mod=1005060097;
LL a[N],b[N],c[N];int r[N*2];
LL ksm(LL a,LL k)
{
    LL ans=1;
    for (;k;k>>=1,a=a*a%mod)
      if (k&1) ans=ans*a%mod;
    return ans;
}
void NTT(int n,LL *a,int id)
{
    int L=0;for (int i=1;i<n;i<<=1) ++L;
    for (int i=0;i<=n;i++) r[i]=(r[i>>1]>>1)|((i&1)<<L-1);
    for (int i=0;i<n;i++)
      if (i<r[i]) swap(a[i],a[r[i]]);
    for (int k=1;k<n;k<<=1)
    {
        LL wn=ksm(5,(mod-1)/(k<<1));
        for (int i=0;i<n;i+=(k<<1))
        {
            LL w=1;
            for (int j=0;j<k;j++,w=w*wn%mod)
            {
                LL x=a[i+j],y=w*a[i+j+k]%mod;
                a[i+j]=(x+y)%mod; a[i+j+k]=(x-y+mod)%mod;
            }
        }
    }
    if (id==-1) reverse(a+1,a+n);
}
void INV(int n,LL *a,LL *b,LL *c)
{
    if (n==1)
    {
        b[0]=ksm(a[0],mod-2);
        return;
    }
    INV((n+1)>>1,a,b,c);int now=1;
    for (now=1;now<(n<<1);now<<=1);
    for (int i=0;i<n;i++) c[i]=a[i];
    for (int i=n;i<now;i++) c[i]=0;
    NTT(now,c,1); NTT(now,b,1);
    for (int i=0;i<=now;i++) b[i]=(2-b[i]*c[i]%mod+mod)%mod*b[i]%mod;
    NTT(now,b,-1); 
    LL inv=ksm(now,mod-2);
    for (int i=0;i<n;i++) b[i]=b[i]*inv%mod;
    for (int i=n;i<now;i++) b[i]=0;
}
int main()
{
    freopen("polypeptide.in","r",stdin);
    freopen("polypeptide.out","w",stdout);
    int e,m;scanf("%d%d",&e,&m);int maxx=0;
    for (int i=1;i<=m;i++) 
    {
        int x;scanf("%d",&x);
        if (x<=e) a[x]=(a[x]-1+mod)%mod;
        maxx=max(maxx,x);
    }
    a[0]=1;int n,L=0;
    for (n=1;n<=maxx*2;n<<=1) L++;
    INV(n,a,b,c);
    printf("%lld",b[e]%mod);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值