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

有了原根和FTT作为基础,NTT非常容易理解

前置知识

正文

众所周知,FFT由于存在大量的复数运算因此会炸精

唔,我们直接观察就能发现原根能把 0p2 这些指数取出不同的数值,进而形成了最少周期为 p1 的环,因此也就满足了 ω 的三个引理

所以我们直接用 gp1n 代替掉 ωn 即可

但是原根有所不同的是如果我们要均匀选取 n 个点就一定要满足 n|(p1),而因为后续分治的原因 n 一定是 2 的整次幂

所以我们可以得到选取质数的条件:p可以表示为r2k+1的形式同时2kn

满足这种条件的质数主要常用的有3个:998244353,1004535809,469762049

同时这三个的原根都是 3

那么我们就可以直接套 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

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

就是去找三个NTT模数,都进行一次 NTT,然后直接 CRT 合并啦

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

下面的是我们要合并的呢

{xa1(modp1)xa2(modp2)xa3(modp3)

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

{xA(modM)xa3(modp3)

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

x=kM+A

且要满足

kM+Aa3(modp3)

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

k(a3A)M1(modp3)

然后就能根据 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诶,不过还是要注意取模呢哼

这样的话任意模数 NTT 好像就没啥惹,在洛谷上有模板题可以看看

posted @   Vsinger_洛天依  阅读(55)  评论(6编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】

阅读目录(Content)

此页目录为空

点击右上角即可分享
微信分享提示