[总结] 仙人NTT的入门

仙人NTT的入门

在快速傅里叶变换中,我们利用 w n 单位复数根实现了消去引理和折半引理
但是由于复数运算的关系,导致精度问题,使人十分捉鸡
那么,有没有什么整数也满足消去引理和折半引理来代替 w n 单位复数根呢?
这就是所谓的原根

那么什么是原根呢?
定义 p 的原根为满足 g ϕ ( p ) 1 ( mod p ) 的整数 g
对于一个质数 p ,如果它存在原根,那么 g i g j ( mod p )
那么我们用 g i 生成的横坐标就不会相同,以此来生成点集表示法

现在我们用 g ϕ ( p ) n 来代替 e 2 π i n
因为我们想利用整数进行计算,那么 ϕ ( p ) n 应该为整数,因为当 p 为质数时, ϕ ( p ) = p 1 ,那么 p 1 0 ( mod 2 q ) , n 2 q

消去引理: w d n d k = w n k
* 证明: w d n d k = g d k ϕ ( p ) d n = g k ϕ ( p ) n = w n k

折半引理: n 为偶数,则 n 次单位复数根的平方的集合就是 n 2 次单位复数根的集合,特别的,每个 n 2 次单位复数根出现两次
* 证明: ( w n k ) 2 = w n 2 k , k [ 0 , n 1 ] [消去引理]

非常nice

所以就和FFT一样一样的啦!

板子!
多项式乘法

#include <bits/stdc++.h>
using namespace std;
const int N = 3000010;
const int G=3,mod=(119<<23)+1;
int n,m,l;
int a[N],b[N],r[N];
int ksm(int a,int b) {
    int ans=1;
    while(b) {
        if(b&1) ans=1ll*ans*a%mod;
        a=1ll*a*a%mod;
        b>>=1;
    }
    return ans;
}
void ntt(int *now,int f) {
    for(int i=0;i<n;++i)
        if(i<r[i]) swap(now[i],now[r[i]]);
    for(int i=1;i<n;i<<=1) {
        int gn=ksm(G,(mod-1)/(i<<1));
        for(int j=0;j<n;j+=(i<<1)) {
            int x,y,g=1;
            for(int k=0;k<i;++k,g=1ll*g*gn%mod) {
                x=now[j+k],y=1ll*g*now[j+k+i]%mod;
                now[j+k]=(x+y)%mod;
                now[j+k+i]=(x-y+mod)%mod;
            }
        }
    }
    if(f!=1) {//在FFT中我们变换了回来,但是NTT就需要单独变换
        int ny=ksm(n,mod-2);
        reverse(a+1,a+n);
        for(int i=0;i<n;++i) a[i]=1ll*a[i]*ny%mod;
    }
}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n;++i) scanf("%d",&a[i]);
    for(int i=0;i<=m;++i) scanf("%d",&b[i]);
    m+=n;
    for(n=1;n<=m;n<<=1) ++l;
    for(int i=0;i<n;++i)
        r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    ntt(a,1);ntt(b,1);
    for(int i=0;i<n;++i) a[i]=1ll*a[i]*b[i]%mod;
    ntt(a,-1);
    for(int i=0;i<=m;++i)
        printf("%d ",a[i]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/force_chl/article/details/80753084
NTT