快速数论变换NTT
单位根
多项式 $x^n=1$ 的根称为 n 次单位根。
设 $\omega_{n}=e^{i\pi}$为复数,
由欧拉公式$e^{ix}=cosx+isinx$
容易验证$\omega_{n}^{n}=1$;
考虑膜意义下的单位根 $x^{n}\equiv 1(mod\ p)$
对于质数 $p=kn+1$,设 为$\mathbb{F}_{p}$中的元素,其中$g$为模$p$的原根,那么显然$\omega_{n}^{n}=1$。
原根
原根,是一个数学符号。设$p$是正整数,$g$是整数,若$g$模$p$的阶等于$φ(p)$,则称$g$为模$p$的一个原根。
那么对质数$p$而言,$\phi(p)=p-1$
阶
设$a$,$p$是整数,$a$和$p$互素,那么:
使$a^{n}\equiv 1(mod\ p)$
成立的最小正整数$n$叫做$a$膜$p$的阶
在上述两种情况下,1,$\omega_{n}^{1}$,$\omega_{n}^{2}$...$\omega_{n}^{n-1}$为$n$个不同的$n$次单位根
$FTT$与$NTT$只在单位根定义上有些许差异,其他都大同小异
由于要快速变换,所以要求$p=k*2^{l}+1$
这样的话原根$g^{k*2^{l}}\equiv 1(mod\ p)$,
而如果$g^{k*2^{l}}$不是2的整数倍的话,在分治\迭代过程中会出现分数指数,即会出现根号。
这样可以处理$lim\leq 2^{l}$的多项式
推荐模数:
模数 | $k$ | $l$ | $g$ |
$998244353$ | $7$$*$$17$ | $23$ | $3$ |
注意:$2^{23}<10^{6}$
code:
#include<bits/stdc++.h> #define ll long long using namespace std; const int N=1<<22; const int P=998244353; const ll g=3; const ll invg=332748118; ll r[N],lim,L,n,m; ll a[N],b[N]; ll ksm(ll x,ll n) { ll ans=1; while(n){ if(n&1)ans=(ans*x)%P; n>>=1; x=(x*x)%P; } return ans; } void ntt(ll *f,int op){ for(int i=0;i<lim;i++){ if(i<r[i]){ swap(f[i],f[r[i]]); } } for(int p=2;p<=lim;p<<=1){ int len=p>>1; ll Wn=ksm(op==1?g:invg,(P-1)/p); for(int j=0;j<lim;j+=p){ ll w=1; for(int k=j;k<j+len;k++){ ll t=w*f[k+len]%P; f[k+len]=(f[k]-t+P)%P; f[k]=(f[k]+t)%P; w=(w*Wn)%P; } } } } int main(){ cin>>n>>m; for(int i=0;i<=n;i++){ scanf("%d",&a[i]); a[i]=(a[i]+P)%P; } for(int i=0;i<=m;i++){ scanf("%d",&b[i]); b[i]=(b[i]+P)%P; } while(1<<L<=n+m)L+=1; lim=1<<L; for(int i=0;i<lim;i++){ r[i]=(r[i>>1]>>1)|((i&1)<<(L-1)); } ntt(a,1); ntt(b,1); for(int i=0;i<lim;i++){ a[i]=a[i]*b[i]%P; } ntt(a,-1); ll inv=ksm(lim,P-2); for(int i=0;i<=n+m;i++){ printf("%lld ",(a[i]*inv)%P); } puts(""); return 0; }