【学习笔记】- 基础数论:快速数论变换

有了原根和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确实NTTFFT快很多,可是在开了O2后的FFT却比NTT快一点呢

任意模数NTT

呐,这个似乎没啥特别难的呢,不过名字却看起来很唬人诶

就是去找三个\(\text {NTT}\)模数,都进行一次 \(\text {NTT}\),然后直接 \(\text {CRT}\) 合并啦

然鹅会爆long long诶,难道要用__int128?怎么办呢

下面的是我们要合并的呢

\[\begin{cases} x\equiv a_1\pmod {p_1} \\ x\equiv a_2\pmod {p_2} \\ x\equiv a_3\pmod {p_3} \end{cases} \]

我们在long long范围内合并前两个

\[\begin{cases} x &\equiv A \pmod M \\ x &\equiv a_3 \pmod {p_3} \\ \end{cases} \]

呃呃,我们发现最后的答案为

\[x=kM+A \]

且要满足

\[kM+A\equiv a_3 \pmod{p_3} \]

唔,这样就可以求出来了诶

\[k \equiv (a_3 - A)M^{-1} \pmod {p_3} \]

然后就能根据 \(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}\) 好像就没啥惹,在洛谷上有模板题可以看看

posted @ 2024-02-17 19:13  Vsinger_洛天依  阅读(47)  评论(6编辑  收藏  举报