NTT相关
一种快速数论变换算法,这种算法是以数论为基础,对样本点为的数论变换,按时间抽取的方法,得到一组等价的迭代方程,有效高速简化了方程中的计算公式·与直接计算相比,大大减少了运算次数。(见快速傅里叶变换)。
在计算机实现多项式乘法中,我们所熟知的快速傅里叶变换(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→ F F T FFT FFT相关
- 好像 N T T NTT NTT比起 F F T FFT FFT来难的知识点更少了emm
NTT的优缺点
优点
-
能取模, 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
NTT前置知识&技能
原根
对于
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
就这么少东西?好像真的只有这么少
NTT(快速数论变换)
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)
NTT板子
这些是定义
#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变换后每一项系数乘上多项式长度的逆元即可
NTT没了…
本人版权意识薄弱……
其实
N
T
T
NTT
NTT还有更高级、更黑科技的用法
任意模数
N
T
T
NTT
NTT慢慢学…