快速数论变换(NTT)

说讲解NTT之前,我们先说简单一下FFT和卷积。

由此可见,我们的FFT最大的用处便是处理卷积问题。比如说CF993E

但在如今的OI界,取模之风逐渐替代了高精之风。我们使用已经过时的double其实并不是什么明智之举。有什么解决它的好办法呢?

有倒是有,但是需要提前学习原根这一概念。

原根:

虽然只看定义会感觉很迷惑,但有一点很明显:并不是所有的数都有原根。具体的:只有2,4,$p^k$,$2p^k$有原根,其中p是除了2以外的任意质数。k是任意正整数。

光有定义什么都做不到,我们需要前人推出来的性质。

性质1:如果g是n的原根,那么$g^0,g^1,...,g^{\phi(n)-1}$在模n的意义下均不同。

性质2:于互质的正整数 𝑔,𝑛 ,设$ 𝑝_1,𝑝_2,⋯,𝑝_𝑘 $为 𝜑(𝑛) 的所有质因数,若 𝑔 是 𝑛 的原根,则对于 ∀𝑖∈[1,𝑘] ,有$g^{\frac{𝜑(𝑛)}{𝑝_𝑖}}\not\equiv 1(mod$ $n)$

由性质2可以得到:对于一个正整数,如果它有原根,那么它最小的一个原根通常很小,所以我们可以从小到大枚举正整数,然后用上个性质检验。、

 

 

 

快速数论变换NTT:

 NTT 和 DFT 最大的差别就在于主单位根的选取,根据原根的性质,如果 𝑝 为质数,那么在 $𝔽_𝑝 $上$ 𝑔^{(𝑝−1)/𝑛}$就是 𝑛 次主单位根。

 

 

 

能被写成 $𝑡⋅2^𝑘+1$形式的质数也被叫作 NTT 模数,常见的 NTT 模数有:469762049、998244353、1004535809

 

现在我们会在 𝑝 为 NTT 模数的时候,做 𝔽_𝑝 上的 NTT 了,那么如果模数不是 NTT 模数该怎么办呢

 

 

假设 𝑝 不是 NTT 模数,设 𝑎𝑛𝑠=𝑎∗𝑏 ,我们要求 𝑎𝑛𝑠%𝑝 ,那么我们可以取互不相同的 NTT 模数 𝑞_1,𝑞_2,⋯,𝑞_𝑘 ,通过 NTT 求出 𝑎𝑛𝑠%𝑞_1,𝑎𝑛𝑠%𝑞_2,⋯𝑎𝑛𝑠%𝑞_𝑘 ,然后用中国剩余定理求出 𝑎𝑛𝑠%𝑝

 

 

至于要取几个 NTT 模数,只要保证 ∏_(𝑖=0)^𝑘▒𝑝_𝑖  大于 𝑎∗𝑏 数组里的最大值即可。一般情况下取 469762049、998244353、1004535809 就足够了,所以这种方法也被称为三模 NTT

 

 

#include <bits/stdc++.h>
#define inc(i,a,b) for(register int i=a;i<=b;i++)
using namespace std;
int n,m;
long long a[4000010],b[4000010];
const int p=998244353;
long long KSM(long long a,long long b){
    long long res=1;
    while(b){
        if(b&1) res=res*a%p;
        a=a*a%p;
        b/=2;
    }
    return res;
}
int rev[4000010],num,limit=1;
void NTT(long long *now,int type){
    inc(i,0,limit-1) if(i<rev[i]) swap(now[i],now[rev[i]]);
    for(int mid=1;mid<limit;mid<<=1){
        long long g=KSM(3,(p-1)/(mid<<1))%p;
        if(type==-1) g=KSM(g,p-2)%p;
        for(int l=0,r=(mid<<1);l<limit;l+=r){            
            for(long long w=1,j=0;j<mid;j++,w=w*g%p){
                long long x=now[l+j],y=w*now[l+j+mid]%p;
                now[l+j]=(x+y)%p;
                now[l+j+mid]=(x-y+p)%p;
            }
        }
    }
}
int main(){
    cin>>n>>m;
    inc(i,0,n) scanf("%lld",&a[i]);
    inc(i,0,m) scanf("%lld",&b[i]);
    while(limit<=(n+m)) limit<<=1,num++;
    inc(i,0,limit-1) rev[i]=((rev[i>>1]>>1)|((i&1)<<(num-1))); 
    //cout<<11111;
    NTT(a,1); NTT(b,1);
    inc(i,0,limit-1) a[i]=(a[i]*b[i])%p;
    NTT(a,-1);
    long long inv=KSM(limit,p-2);
    inc(i,0,n+m) printf("%lld ",a[i]*inv%p);
}

 

posted @ 2020-06-24 14:11  神之右大臣  阅读(794)  评论(0编辑  收藏  举报