比FFT还容易明白的NTT(快速数论变换)

NTT  N T T 相关

一种快速数论变换算法,这种算法是以数论为基础,对样本点为的数论变换,按时间抽取的方法,得到一组等价的迭代方程,有效高速简化了方程中的计算公式·与直接计算相比,大大减少了运算次数。(见快速傅里叶变换)。
在计算机实现多项式乘法中,我们所熟知的快速傅里叶变换(FFT)是基于n次单位根 (omega) 的优秀性质实现的,而由于其计算时会使用正弦函数和余弦函数,在不断运算时无法避免地会产生精度误差。而多项式乘法有些时候会建立在模域中,在对一些特殊的大质数取模时,便可以考虑用原根g来代替 ,而这些特殊的大质数的原根恰好满足 的某些性质,这使得多项式乘法在模域中也可以快速的分治合并。
——百度百科

NTT(Number Theoretic Transform),中文名快速数论变换
FFT  F F T 一样, NTT  N T T 也用来加速多项式乘法,不过 NTT  N T T 最大的优点是可以取模
或者可以理解为 NTT  N T T FFT  F F T 取模升级版

  • 好像 NTT  N T T 比起 FFT  F F T 来难的知识点更少了emm

NTT  N T T 的优缺点

优点

  • 能取模 FFT  F F T 复数你给我取个模……?

  • 没有精度差 FFT  F F T 浮点数精度怎么也会出点问题

  • 由于均为整数操作(虽然取模多), NTT  N T T 常数小,通常比一大堆浮点运算的 FFT  F F T (其实这是放屁)

    《比FFT还容易明白的NTT(快速数论变换)》

    《比FFT还容易明白的NTT(快速数论变换)》

    《比FFT还容易明白的NTT(快速数论变换)》

NTT  N T T 小数据下表现良好……

缺点

  • 多项式的系数都必须是整数

  • 模数有限制而且 NTT  N T T 题目的模数通常都是相同 998244353  998244353

NTT  N T T 前置知识&技能

原根

对于 g,pZ  g , p ∈ Z ,如果 g i modp(1ip1)  g i m o d p ( 1 ⩽ i ⩽ p − 1 ) 的值互不相同,则称 g  g p  p 原根
或者说 i,j(1i,jp1,i<j),g i modpg j modp  ∀ i , j ( 1 ⩽ i , j ⩽ p − 1 , i < j ) , g i m o d p ≠ g j m o d p ,那么 g  g p  p 的原根
原根没什么快速求法,只能暴力枚举判断
通常模数常见的有 998244353,1004535809,469762049  998244353 , 1004535809 , 469762049 ,这几个的原根都是 3  3
就这么少东西?好像真的只有这么少

NTT  N T T (快速数论变换)

FFT  F F T 可以优化是因为 ω  ω 有着神奇且优秀的性质
NTT  N T T 呢?其实原根也有这类性质!

NTT  N T T 里,我们可以拿原根来代替 FFT  F F T 的单位根
具体就是,当合并区间的长度为 len=2mid  l e n = 2 m i d 时,单位根为 cos2πlen +isin2πlen =cosπmid +isinπmid   cos ⁡ 2 π l e n + i sin ⁡ 2 π l e n = cos ⁡ π m i d + i sin ⁡ π m i d
而原根即为 g p1len  =g p12mid    g p − 1 l e n = g p − 1 2 m i d
注意大多题目的模数 p=998244353  p = 998244353 ,此时 g=3  g = 3 ,即可代入原根计算 NTT  N T T
如果理解了 FFT  F F T 的话, NTT  N T T 也就迎刃而解了
时间复杂度 O(nlog 2 n)  O ( n log 2 ⁡ n )

NTT  N T T 板子

#define g 3//模数的原根
#define mod 998244353//通常情况下的模数

int pow(int x,int y)//快速幂
{
    ll z=1ll*x,ans=1ll;
    for (;y;y/=2,z=z*z%mod)if (y&1)ans=ans*z%mod;//注意精度
    return (int)ans%mod;
}

以下是 NTT  N T T 板子,基本就是拿 FFT  F F T 的板子改几下就好了

void ntt(int a[],int inv)
{
    int bit=0;
    while ((1<<bit)<n)bit++;
    fo(i,0,n-1)
    {
        rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
        if (i<rev[i])swap(a[i],a[rev[i]]);
    }//前面和FFT一样
    for (int mid=1;mid<n;mid*=2)
    {
        int temp=pow(g,(mod-1)/(mid*2));//用原根代替单位根
        if (inv==-1)temp=pow(temp,mod-2);//逆变换则乘上逆元
        for (int i=0;i<n;i+=mid*2)
        {
            int omega=1;
            for (int j=0;j<mid;j++,omega=1ll*omega*temp%mod)
            {
                int x=a[i+j],y=1ll*omega*a[i+j+mid]%mod;
                a[i+j]=x+y,a[i+j+mid]=x-y;
                a[i+j]=(a[i+j]%mod+mod)%mod,a[i+j+mid]=(a[i+j+mid]%mod+mod)%mod;//注意取模
            }
        }//大体流程和FFT无异
    }
}

FFT  F F T 一样,也只能处理 n  n 2  2 的次幂的多项式
NTT  N T T 调用和 FFT  F F T 一模一样,注意 longlong  l o n g l o n g 和除法都变成乘逆元
NTT  N T T 变换后每一项系数乘上模数 p  p 逆元即可

NTT  N T T 这就没了……?

本人版权意识薄弱……

其实 NTT  N T T 还有更高级、更黑科技的用法
任意模数NTT以后慢慢学好了今天一天搞完NTT肾都虚了

点赞