【学习笔记】- 基础数论:快速数论变换
有了原根和FTT作为基础,NTT非常容易理解
前置知识
-
多项式相关基础概念
-
中国剩余定理
正文
众所周知,FFT由于存在大量的复数运算因此会炸精
唔,我们直接观察就能发现原根能把 \(0\sim p-2\) 这些指数取出不同的数值,进而形成了最少周期为 \(p-1\) 的环,因此也就满足了 \(\omega\) 的三个引理
所以我们直接用 \(g^{\frac{p-1}{n}}\) 代替掉 \(\omega_n\) 即可
但是原根有所不同的是如果我们要均匀选取 \(n\) 个点就一定要满足 \(n|(p-1)\),而因为后续分治的原因 \(n\) 一定是 \(2\) 的整次幂
所以我们可以得到选取质数的条件:\(p\)可以表示为\(r2^k+1\)的形式同时\(2^k\ge n\)
满足这种条件的质数主要常用的有\(3\)个:\(998244353,1004535809,469762049\)
同时这三个的原根都是 \(3\) 诶
那么我们就可以直接套 \(\text{FFT}\) 的板子并略微修改即可
const int mod=998244353;
inline int qpow(int x,int y) {
int ret(1);
for(;y;y>>=1,x=x*x%mod)
if(y&1)
ret=ret*x%mod;
return ret;
}
int A[N],B[N],rev[N],tot=1;
void NTT(int *a,int inv) {
for(int i(0);i<=tot;++i)
if(rev[i]>i)
swap(a[i],a[rev[i]]);
for(int m=2;m<=tot;m<<=1){
int w1=qpow(3,(mod-1)/m);
if(inv==-1)
w1=qpow(w1,mod-2);
for(int i(0);i<tot;i+=m) {
int w=1;
for(int k=0;k<(m>>1);++k){
int u(a[i+k]),t(w*a[i+k+(m>>1)]%mod);
a[i+k]=(u+t)%mod;
a[i+k+(m>>1)]=(u-t)%mod;
w=w*w1%mod;
}
}
}
if(inv==-1){
for(int i=0,mid=qpow(tot,mod-2);i<tot;++i)
a[i]=a[i]*mid%mod;
}
}
signed main(){
int n,m; FastI>>n>>m;
for(int i=0;i<=n;++i) FastI>>A[i];
for(int i=0;i<=m;++i) FastI>>B[i];
while(tot<=n+m)
tot<<=1;
for(int i=0;i<=tot;++i)
rev[i]=(rev[i>>1]>>1)|(i&1?(tot>>1):0);
NTT(A,1);NTT(B,1);
for(int i=0;i<=tot;++i)
A[i]=A[i]*B[i]%mod;
NTT(A,-1);
int tmp=qpow(tot,mod-2);
for(int i=0;i<=n+m;++i)
FastO<<(A[i]+mod)%mod*tmp%mod<<" ";
}
由于 998244353
的原根是 3
所以就直接把 3
放上去惹
Q:诶这里全都是整数,是不是比 FFT
快呢
A:唔,我们发现如果不开O2
确实NTT
比FFT
快很多,可是在开了O2后的FFT
却比NTT
快一点呢
任意模数NTT
呐,这个似乎没啥特别难的呢,不过名字却看起来很唬人诶
就是去找三个\(\text {NTT}\)模数,都进行一次 \(\text {NTT}\),然后直接 \(\text {CRT}\) 合并啦
然鹅会爆long long
诶,难道要用__int128
?怎么办呢
下面的是我们要合并的呢
我们在long long
范围内合并前两个
呃呃,我们发现最后的答案为
且要满足
唔,这样就可以求出来了诶
然后就能根据 \(k\) 的值带入 \(x=kM+A\) 求解了
但是似乎这个任意模数
还是有一些限制存在的,比如模数不能超int
,不然就要增加NTT
次数,还可能会出现高精度取模的情况,常数直接爆炸惹
第一步的合并可能也会爆long long
,所以要龟速乘?但是常数巨大诶,出题人真的会放过去吗
解法有几个呢
首先我们可以使用这个板子(快速乘/光速乘)啦
inline int Mul(int a,int b,const int P){
a=(a%P+P)%P,b=(b%P+P)%P;
return ((a*b-(int)((long double)a/P*b+1e-6)*P)%P+P)%P;
}
但是这个有误差诶,不过似乎问题不大
我们还可以使用__int128
诶,不过还是要注意取模呢哼
这样的话任意模数 \(\text{NTT}\) 好像就没啥惹,在洛谷上有模板题可以看看