任意模数NTT(学习笔记)

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

F F T FFT 有时候会被卡精度?所以可能会有模数,有了模数以后就需要模数的原根。

原根是什么?(留坑待填)

N T T NTT 有很多种解决方法
1. 1. 特殊模数
( 2 k + 1 ) ( p 1 ) ( p 1 ) > D F T (2k+1)|(p−1),(p−1)>DFT的长度 ,可以直接暴力求原根 g g ,用 g g 代替单位复数根

2. 2. 一般模数

  • 三模数法(9次DFT)
    如果这个模数的原根不好求,而模数又很大,可以用三个模数
    要求 m o d 1 × m o d 2 × m o d 3 n × p 2 mod1\times mod2\times mod3\ge n\times p^2
    常用 998244353 , 1004535809 , 469762049 998244353,1004535809,469762049 ,因为他们的原根都是 3 3
    对这几个模数分别做 D F T DFT ,然后用中国剩余定理合并,然后对原模数取模即可
    中国剩余定理部分
    a n s c 1   m o d   m 1 ans\equiv c1\ mod\ m1
    a n s c 2   m o d   m 2 ans\equiv c2\ mod\ m2
    a n s c 3   m o d   m 3 ans\equiv c3\ mod\ m3
    如果直接合并的话会爆 l o n g   l o n g long\ long ,可以先合并两个,再用奇技淫巧合并第三个
    合并前两个:
    a n s ( c 1 × m 2 × i n v ( m 2 , m 1 ) + c 2 × m 1 × i n v ( m 1 , m 2 ) ) m o d   ( m 1 × m 2 ) ans\equiv (c1\times m2\times inv(m2,m1)+c2\times m1\times inv(m1,m2)) mod\ (m1\times m2)
    其中 i n v ( x , y ) inv(x,y) 表示 x x y y 取模的逆元。
    把上式化简为 a n s C   m o d   M ans\equiv C\ mod\ M
    a n s = x × M + C = y × m 3 + c 3 ans=x\times M+C=y\times m3+c3
    接下来很重要:求出 x   m o d   m 3 x\ mod\ m3 意义下的值:
    x ( c 3 C ) × M 1   m o d   m 3 x\equiv (c3-C)\times M^{-1}\ mod\ m3
    这样就可以算出右半部分的值 q q ,令 x = k × m 3 + q x=k\times m3+q ,代入 a n s ans 得:
    a n s = k × m 1 × m 2 × m 3 + q × M + C ans=k\times m1\times m2\times m3+q\times M+C
    因为 a n s [ 0 , m 1 × m 2 × m 3 ) ans\in [0,m1\times m2\times m3) ,所以 k = 0 k=0 ,于是 a n s ans 就可直接计算。
    做9次DFT,常数极大
    有一道模板题luogu4245
    直接用三模数法,代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define LL long long
#define maxn 400005
using namespace std;
const LL mod1=998244353,mod2=1004535809,mod3=469762049,g=3;
const LL M=1LL*mod1*mod2;

inline int rd(){
    int x=0,f=1;char c=' ';
    while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
    while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
    return x*f; 
}

int n,m,p,rev[maxn],limit=1,l;
LL a[3][maxn],b[3][maxn],ans[maxn];

inline LL mul(LL x,int k,LL MOD){
    LL ret=0;
    while(k){
        if(k&1) (ret+=x)%=MOD;
        (x+=x)%=MOD; k>>=1;
    } return ret%MOD;
}

inline LL qpow(LL x,int k,int MOD){
    LL ret=1;
    while(k){
        if(k&1) ret=ret*x%MOD;
        x=x*x%MOD; k>>=1;
    } return ret%MOD;
}

inline void NTT(LL *F,int type,int MOD){
    for(int i=0;i<limit;i++){
        F[i]%=MOD;
        if(i<rev[i]) swap(F[i],F[rev[i]]);
    }
    for(int mid=1;mid<limit;mid<<=1){
        LL Wn=qpow(g,type==1?(MOD-1)/(mid<<1):(MOD-1-(MOD-1)/(mid<<1)),MOD);//!
        for(int r=mid<<1,j=0;j<limit;j+=r){
            LL w=1;
            for(int k=0;k<mid;k++,w=w*Wn%MOD){
                LL x=F[j+k],y=w*F[j+mid+k]%MOD;
                F[j+k]=(x+y)%MOD; F[j+mid+k]=(x-y+MOD)%MOD;
            }
        }
    }
    if(type==-1){
        LL INV=qpow(limit,MOD-2,MOD);//除以limit
        for(int i=0;i<limit;i++) F[i]=F[i]*INV%MOD; 
    }
}

inline void CRT(){
    for(int i=0;i<limit;i++){
        LL tmp=0;
        (tmp+=mul(a[0][i]*mod2%M,qpow(mod2,mod1-2,mod1),M))%=M;
        (tmp+=mul(a[1][i]*mod1%M,qpow(mod1,mod2-2,mod2),M))%=M;
        a[1][i]=tmp;
    }
    for(int i=0;i<limit;i++){//x=(c3-C)/M mod mod3
        LL tmp=(a[2][i]-a[1][i]%mod3+mod3)%mod3*qpow(M%mod3,mod3-2,mod3)%mod3;
        ans[i]=(M%p*tmp%p+a[1][i]%p)%p;
    }
}

inline void solve(int x,int MOD){
    NTT(a[x],1,MOD); NTT(b[x],1,MOD);
    for(int i=0;i<limit;i++) a[x][i]=a[x][i]*b[x][i]%MOD;
    NTT(a[x],-1,MOD);
}

int main(){
    n=rd(); m=rd(); p=rd();
    for(int i=0;i<=n;i++){
        int x=rd();
        for(int j=0;j<3;j++) a[j][i]=x%p;
    }
    for(int i=0;i<=m;i++){
        int x=rd();
        for(int j=0;j<3;j++) b[j][i]=x%p;
    }
    while(limit<=n+m) limit<<=1,++l;
    for(int i=0;i<limit;i++)
        rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
    solve(0,mod1); solve(1,mod2); solve(2,mod3);
    CRT();
    for(int i=0;i<=n+m;i++) printf("%lld ",ans[i]);
    return 0;
}
  • 拆系数法(7次DFT)
    将两个多项式系数拆开,然后发现可以分成三部分计算,不很常用而且精度掉的厉害,这里先不做详细解释

  • MTT(4次DFT)
    奇技淫巧,话说三模数NTT卡卡常多预处理一些应该都能过去,不行就开O2,跑的蛮快的(反正省选也开O2 ),这个东西先放着,以后闲得没事 再来看

猜你喜欢

转载自blog.csdn.net/sizeof_you/article/details/84557728