Skip to main content

快速数论变换

Summary

快速数论变换(NTT)把 FFT 从复数域搬到模 p 的整数域,用原根的幂代替单位根,既精确又天然适配带取模的题目。要求模数形如 p=q·2^c+1,此时原根 g 的相应幂在模 p 下扮演 n 次单位根的角色,前提是变换长度整除 p-1。折半、消去两条引理照样成立,FFT 的蝶形结构得以原样套用,只需替换旋转因子并在逆变换后整体乘 n 的逆元。列出常用 NTT 模数及原根,并给出迭代实现。例题为洛谷 P3803。

参考资料​

思想​

快速傅里叶变换(FFT) 在复数域上用单位根 ωn=e2πi/n\omega_n=e^{2\pi i/n} 做变换,浮点运算存在精度误差。快速数论变换(Number Theoretic Transform,NTT)把整个过程搬到模 pp 的整数域上,用 原根(Primitive Root)的幂代替单位根,既精确又天然适合带取模的题目。

要求模数 pp 为 NTT 模数,形如 p=q⋅2c+1p=q\cdot 2^c+1。设 gg 是模 pp 的原根,则 gn=gp−1ng_n=g^{\frac{p-1}{n}} 在模 pp 意义下扮演 nn 次单位根的角色,前提是变换长度 n∣(p−1)n\mid(p-1),即 nn 是 22 的幂且 2c≥n2^c\ge n。

常用的 NTT 模数及其原根:

模数 pp分解原根 gg
998244353998244353119×223+1119\times 2^{23}+133
10045358091004535809479×221+1479\times 2^{21}+133
4697620494697620497×226+17\times 2^{26}+133

单位根的折半引理、消去引理在原根下同样成立,因此 FFT 的蝶形结构可以原样套用:只需把 ωn\omega_n 换成 gp−1ng^{\frac{p-1}{n}},逆变换时换成它的逆元 g−p−1ng^{-\frac{p-1}{n}},最后整体乘上 n−1n^{-1}。

实现​

423 Bcpp
using ll=long long;
const int mod=998244353,G=3,Gi=332748118;
void ntt(int *f,int n,int type)
{
for(int i=0;i<n;i++)if(i<r[i])swap(f[i],f[r[i]]);
for(int k=1;k<n;k<<=1)
{
int step=Pow(type==1?G:Gi,(mod-1)/(k<<1));
for(int i=0;i<n;i+=k<<1)
{
int cur=1;
for(int j=0;j<k;j++)
{
int x=f[i+j],y=(ll)cur*f[i+j+k]%mod;
f[i+j]=(x+y)%mod;
f[i+j+k]=(x-y+mod)%mod;
cur=(ll)cur*step%mod;
}
}
}
}

其中 Gi=332748118Gi=332748118 是原根 33 在模 998244353998244353 下的逆元。逆变换后还需将每一项乘以 nn 的逆元。

例题​

给定一个 nn 次多项式 F(x)F(x),和一个 mm 次多项式 G(x)G(x)。

请求出 F(x)F(x) 和 G(x)G(x) 的乘积。