跳到主要内容

快速数论变换(FNTT)

参考资料

思想

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

要求模数 ppNTT 模数,形如 p=q2c+1p=q\cdot 2^c+1。设 gg 是模 pp 的原根,则 gn=gp1ng_n=g^{\frac{p-1}{n}} 在模 pp 意义下扮演 nn 次单位根的角色,前提是变换长度 n(p1)n\mid(p-1),即 nn22 的幂且 2cn2^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 换成 gp1ng^{\frac{p-1}{n}},逆变换时换成它的逆元 gp1ng^{-\frac{p-1}{n}},最后整体乘上 n1n^{-1}

实现

424 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=qpow(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) 的乘积。