NTT
N T T NTT NTT和 F T T FTT FTT十分相似,在 F T T FTT FTT中使用了单位复根 ω \omega ω来取函数值,在 N T T NTT NTT中则使用原根 g g g来取函数值。在 N T T NTT NTT中,所有的数都是模意义下的。
g g g的一些性质
- g 1 , g 2 , ⋯ , g p − 1 g^1,g^2,\cdots,g^{p-1} g1,g2,⋯,gp−1在模 p p p意义下互不相同
- 根据欧拉定理可知 g p − 1 ≡ 1 g^{p-1} \equiv 1 gp−1≡1
用 g g g来替换 ω \omega ω实现 F F T FFT FFT到 N T T NTT NTT的转化
其实只要 g g g能够在模意义下实现 ω \omega ω的性质就行了。
假设 p = q n + 1 p=qn+1 p=qn+1,其中 n n n是一个 2 2 2的幂,我们钦定在对 f n ( x ) f_n(x) fn(x)进行 N T T NTT NTT时使用的单位原根是 g n g_n gn,其计算方式为 g n = g q = g p − 1 n g_n=g^{q}=g^{\frac{p-1}{n}} gn=gq=gnp−1
下面考虑 ω \omega ω在 F F T FFT FFT中使用到的性质:
- ω 2 n 2 i = ω n i \omega_{2n}^{2i}=\omega_{n}^{i} ω2n2i=ωni, g 2 n 2 i = g p − 1 2 n ⋅ 2 i = g p − 1 n ⋅ i = g n i g_{2n}^{2i}=g^{\frac{p-1}{2n}·2i}=g^{\frac{p-1}{n}·i}=g_{n}^{i} g2n2i=g2np−1⋅2i=gnp−1⋅i=gni
- ω n n 2 = − 1 \omega_{n}^{\frac{n}{2}}=-1 ωn2n=−1, g n n 2 = g p − 1 2 = g p − 1 = 1 g_{n}^{\frac{n}{2}}=g^{\frac{p-1}{2}}= \sqrt {g^{p-1}}=\sqrt 1 gn2n=g2p−1=gp−1=1,根据二次探索定理可知,在模意义下 1 \sqrt 1 1只有两种取值,即 1 1 1或 p − 1 p-1 p−1,根据原根的性质一排除取值为 1 1 1的可能,那么 g n n 2 = 1 = p − 1 = − 1 g_{n}^{\frac{n}{2}}=\sqrt 1=p-1=-1 gn2n=1=p−1=−1
- ∀ k ∈ Z , ω n k n = 1 \forall k \in Z,\omega_{n}^{kn}=1 ∀k∈Z,ωnkn=1, ∀ k ∈ Z , g n k n = ( g n n ) k = ( g p − 1 ) k = 1 \forall k \in Z,g_{n}^{kn}=(g_{n}^{n})^k=(g^{p-1})^k=1 ∀k∈Z,gnkn=(gnn)k=(gp−1)k=1
从 F F T FFT FFT到 N T T NTT NTT
我们只要把单位复根替换为单位原根即可,一切计算都在模意义下进行
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
const int N=1<<22;
int f[N],g[N],h[N];
int r[N];
int nf,ng,nh=1;
int ksm(int x,int y){
int res=1;
while (y){
if (y&1) res=1ll*res*x%mod;
x=1ll*x*x%mod;
y>>=1;
}
return res;
}
void FFT(int *f,int rev){//rev=1->DFT rev=-1->IDFT
for (int i=0;i<nh;++i) if (r[i]<i) swap(f[i],f[r[i]]);
for (int l=2;l<=nh;l<<=1){
for (int i=0;i<nh;i+=l){
int step=ksm(3,(mod-1)/l),mul=1;
for (int j=0;j<(l>>1);++j){
int tmp=f[i+j+(l>>1)];
f[i+j+(l>>1)]=(1ll*f[i+j]-1ll*mul*tmp%mod+mod)%mod;
f[i+j]=(1ll*f[i+j]+1ll*mul*tmp%mod)%mod;
mul=1ll*mul*step%mod;
}
}
}
if (rev==-1){
reverse(f+1,f+nh);
int inv=ksm(nh,mod-2);
for (int i=0;i<nh;++i) f[i]=1ll*f[i]*inv%mod;
}
}
int main(){
scanf("%d%d",&nf,&ng);
while (nh-1<nf+ng) nh<<=1;
for (int i=0;i<=nf;++i) scanf("%d",&f[i]);
for (int i=0;i<=ng;++i) scanf("%d",&g[i]) ;
r[0]=0;
for (int i=1;i<nh;++i) r[i]=(r[i>>1]>>1)|(i&1?(nh>>1):0);
FFT(f,1);
FFT(g,1);
for (int i=0;i<nh;++i) h[i]=1ll*f[i]*g[i]%mod;
FFT(h,-1);
for (int i=0;i<=nf+ng;++i) printf("%lld ",h[i]);
}