快速数论变换NTT学习笔记

本文介绍了FFT(快速傅里叶变换)的基本原理,包括消去原理、对称原理和原根的概念。同时提到了精度问题以及如何通过HackFFT解决。文章还引出了NTT(快速数论变换),指出在模数计算中,原根可以用于实现类似FFT的功能,并提供了代码示例。最后,文章提到了单位根反演的应用。

前言

这篇文章中我介绍了什么是 FFT \text{FFT} FFT ,但是在文中我们也说了一嘴这玩意是有精度误差的,三角函数和复数导致我们不得不进行取整操作。

只要毒瘤出题人原因,那就可以 Hack FFT \text{Hack FFT} Hack FFT

Tips:

根据《NOI大纲》的内容,卡精度和卡常数通常是不允许的。

所以 FFT \text{FFT} FFT 通常不会寄,不必过度焦虑。

但是 NTT \text{NTT} NTT 本身并不难。

这时候我们就需要引入快速数论变换 NTT \text{NTT} NTT( Fast Number Theory Transform \text{Fast Number Theory Transform} Fast Number Theory Transform,不过我们通常省略那个 F \text F F)。

首先我们要明确一个方向,就是 FFT \text{FFT} FFT 的原理是单位根的几个性质:

  • 消去原理: ω t n t k = ω n k \omega_{tn}^{tk}=\omega_{n}^k ωtntk=ωnk
  • 对称原理: ω n k = − ω n k + n 2 \omega_{n}^{k}=-\omega_n^{k+\frac n 2} ωnk=ωnk+2n
  • ω n k = ( ω n 1 ) k \omega_{n}^k=(\omega_n^1)^k ωnk=(ωn1)k
  • ω n i ≠ ω n j ( i ≠ j ) \omega_n^i\not=\omega_n^j(i\not=j) ωni=ωnj(i=j)

也就是说,只要满足以上条件,也就可以用类似的方法实现。

我们发现,数论里的原根,和这个很像啊!所以直接使用即可。

代码实现

经典的模数 998244353 998244353 998244353 的原根为 3 3 3,这是常用的组合。

直接替换 ω \omega ω 即可。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const LL N=1e6;
const LL mod=998244353;
const LL G=3;
LL ksm(LL x,LL y)
{
	LL ans=1;
	while(y)
	{
		if(y&1)ans=ans*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ans;
}
LL n,m,Gi,lim,len=1,r[N],x,ans[N];
LL a[N],b[N];
void FFT(LL *a,LL inv)
{
	for(int i=0;i<=len-1;i++)
	{
		if(i<r[i])swap(a[i],a[r[i]]);
	}
	for(int l=2;l<=len;l*=2)
	{
		LL omg=ksm(G,(mod-1)/l);
		if(inv)omg=ksm(Gi,(mod-1)/l);
		LL m=l/2;
		for(int j=0;j<=len-1;j+=l)
		{
			LL x=1;
			for(int i=0;i<=m-1;i++)
			{
				LL t=x;
				t=a[i+j+m]*t%mod;
				a[i+j+m]=(a[i+j]-t+mod)%mod,a[i+j]=(a[i+j]+t)%mod;
				x=x*omg%mod;
			}
		}
	}
}
int main()
{
	scanf("%lld%lld",&n,&m);
	Gi=ksm(G,mod-2);
	while(len<=n+m)len*=2,lim++;
	for(int i=0;i<=len-1;i++)
	{
		LL sum=0;
		for(int j=0;j<=lim-1;j++)sum|=((i>>j)&1)<<(lim-j-1);
		r[i]=sum;
	}
	for(int i=0;i<=n;i++)
	{
		scanf("%lld",&a[i]);
	}
	for(int i=0;i<=m;i++)
	{
		scanf("%lld",&b[i]);
	}
	FFT(a,0),FFT(b,0);
	for(int i=0;i<=len-1;i++)
	{
		a[i]=a[i]*b[i]%mod;
	}
	FFT(a,1);
	LL inv=ksm(len,mod-2);
	for(int i=0;i<=n+m;i++)
	{
		ans[i]=a[i]*inv%mod;
		printf("%lld ",ans[i]);
	}
	return 0;
}

扩展

通常情况下在模意义都会使用原根来替换单位根,比如单位根反演就可以使用原根。

这里简单展示一下单位根反演的结论:
[ n ∣ k ] = 1 n ∑ i = 0 n − 1 ω n k i [n|k]=\frac 1 n \sum_{i=0}^{n-1}\omega_{n}^{ki} [nk]=n1i=0n1ωnki

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值