一种快速数论变换算法,这种算法是以数论为基础,对样本点为的数论变换,按时间抽取的方法,得到一组等价的迭代方程,有效高速简化了方程中的计算公式·与直接计算相比,大大减少了运算次数。(见快速傅里叶变换)。
在计算机实现多项式乘法中,我们所熟知的快速傅里叶变换(FFT)是基于n次单位根 (omega) 的优秀性质实现的,而由于其计算时会使用正弦函数和余弦函数,在不断运算时无法避免地会产生精度误差。而多项式乘法有些时候会建立在模域中,在对一些特殊的大质数取模时,便可以考虑用原根g来代替 ,而这些特殊的大质数的原根恰好满足 的某些性质,这使得多项式乘法在模域中也可以快速的分治合并。
——百度百科
NTT(Number Theoretic Transform),中文名快速数论变换
和 F F T FFT FFT一样, N T T NTT NTT也用来加速多项式乘法,不过 N T T NTT NTT最大的优点是可以取模
或者可以理解为 N T T NTT NTT是 F F T FFT FFT取模升级版
- 好像 N T T NTT NTT比起 F F T FFT FFT来难的知识点更少了emm
优点
能取模, F F T FFT FFT的复数你给我来取个模
没有精度差, F F T FFT FFT浮点数的精度怎么也会出一点问题
由于均为整数操作(虽然取模多), N T T NTT NTT常数小,通常比一大堆浮点运算的 F F T FFT FFT要快(其实这是放屁)
我只能说 N T T NTT NTT小数据下表现良好…
缺点
多项式的系数都必须是整数
模数有限制, N T T NTT NTT题的模数通常都是相同的 998244353 998244353 998244353
其实这些模数的原根通常都是 3 3 3
原根
对于 g , p ∈ Z g,p\in Z g,p∈Z,如果 g i m o d p ( 1 ⩽ i ⩽ p − 1 ) g^imodp(1\leqslant i\leqslant p-1) gimodp(1⩽i⩽p−1)的值互不相同,则称 g g g为 p p p的原根
或者说 ∀ i , j ( 1 ⩽ i , j ⩽ p − 1 , i < j ) , g i m o d p ≠ g j m o d p ∀i,j(1\leqslant i,j\leqslant p-1,i<j),g^imodp≠g^jmodp ∀i,j(1⩽i,j⩽p−1,i<j),gimodp̸=gjmodp,那么 g g g为 p p p的原根
原根没什么快速求法,只能暴力枚举判断
通常模数常见的有 998244353 , 1004535809 , 469762049 998244353,1004535809,469762049 998244353,1004535809,469762049,这几个的原根都是 3 3 3
就这么少东西?好像真的只有这么少
F F T FFT FFT可以大大优化是因为 ω \omega ω有着神奇且优秀的性质
其实原根也有这种性质!
在 N T T NTT NTT里,我们可以拿原根来代替 F F T FFT FFT的单位根
具体就是,当合并区间的长度为 l e n = 2 m i d len=2mid len=2mid时,单位根为 cos 2 π l e n + i sin 2 π l e n = cos π m i d + i sin π m i d \cos{2\pi\over len}+i\sin{2\pi\over len}=\cos{\pi \over mid}+i\sin{\pi \over mid} coslen2π+isinlen2π=cosmidπ+isinmidπ
而原根即为 g p − 1 l e n = g p − 1 2 m i d g^{p-1\over len}=g^{p-1\over 2mid} glenp−1=g2midp−1
注意大多题目的模数 p = 998244353 p=998244353 p=998244353,此时 g = 3 g=3 g=3,即可代入原根计算 N T T NTT NTT了
如果理解了 F F T FFT FFT的话, N T T NTT NTT也就迎刃而解了
时间复杂度 O ( n log 2 n ) O(n\log_2n) O(nlog2n)
这些是定义
#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;
}
这个是 N T T NTT NTT板子,拿 F F T FFT FFT的改那么几下就好了
inline void ntt(int a[],int len,int inv)
{
int bit=0;
while ((1<<bit)<len)++bit;
fo(i,0,len-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<len;mid*=2)
{
int tmp=pow(g,(mod-1)/(mid*2));//原根代替单位根
if (inv==-1)tmp=pow(tmp,mod-2);//逆变换则乘上逆元
for (int i=0;i<len;i+=mid*2)
{
int omega=1;
for (ll j=0;j<mid;++j,omega=omega*tmp%mod)
{
int x=a[i+j],y=omega*a[i+j+mid]%mod;
a[i+j]=(x+y)%mod,a[i+j+mid]=(x-y+mod)%mod;//注意取模
}
}//大体和FFT差不多
}
}
和 F F T FFT FFT一样, N T T NTT NTT也只能处理 n n n为 2 2 2的次幂的多项式
N T T NTT NTT调用和 F F T FFT FFT一模一样,注意 l o n g l o n g long long longlong和除法都变成乘逆元
逆 N T T NTT NTT变换后每一项系数乘上多项式长度的逆元即可
本人版权意识薄弱……
- 本博客部分知识学习于
- https://blog.csdn.net/lym01803/article/details/62891985
- https://www.cnblogs.com/yousiki/p/6426556.html
- https://www.cnblogs.com/candy99/p/6641972.html
其实 N T T NTT NTT还有更高级、更黑科技的用法
任意模数 N T T NTT NTT慢慢学…