比FFT还容易明白的NTT(快速数论变换)

NTT相关

一种快速数论变换算法,这种算法是以数论为基础,对样本点为的数论变换,按时间抽取的方法,得到一组等价的迭代方程,有效高速简化了方程中的计算公式·与直接计算相比,大大减少了运算次数。(见快速傅里叶变换)。
在计算机实现多项式乘法中,我们所熟知的快速傅里叶变换(FFT)是基于n次单位根 (omega) 的优秀性质实现的,而由于其计算时会使用正弦函数和余弦函数,在不断运算时无法避免地会产生精度误差。而多项式乘法有些时候会建立在模域中,在对一些特殊的大质数取模时,便可以考虑用原根g来代替 ,而这些特殊的大质数的原根恰好满足 的某些性质,这使得多项式乘法在模域中也可以快速的分治合并。
——百度百科

NTT(Number Theoretic Transform),中文名快速数论变换
F F T FFT FFT一样, N T T NTT NTT也用来加速多项式乘法,不过 N T T NTT NTT最大的优点是可以取模
或者可以理解为 N T T NTT NTT F F T FFT FFT取模升级版

  • 好像 N T T NTT NTT比起 F F T FFT FFT来难的知识点更少了emm

NTT的优缺点

优点

  • 能取模 F F T FFT FFT的复数你给我来取个模

  • 没有精度差 F F T FFT FFT浮点数的精度怎么也会出一点问题

  • 由于均为整数操作(虽然取模多), N T T NTT NTT常数小,通常比一大堆浮点运算的 F F T FFT FFT(其实这是放屁)

我只能说 N T T NTT NTT小数据下表现良好…

缺点

  • 多项式的系数都必须是整数

  • 模数有限制 N T T NTT NTT题的模数通常都是相同 998244353 998244353 998244353

  • 其实这些模数的原根通常都是 3 3 3


NTT前置知识&技能

原根

对于 g , p ∈ Z g,p\in Z g,pZ,如果 g i m o d p ( 1 ⩽ i ⩽ p − 1 ) g^imodp(1\leqslant i\leqslant p-1) gimodp(1ip1)的值互不相同,则称 g g g p p p原根
或者说 ∀ i , j ( 1 ⩽ i , j ⩽ p − 1 , i &lt; j ) , g i m o d p ≠ g j m o d p ∀i,j(1\leqslant i,j\leqslant p-1,i&lt;j),g^imodp≠g^jmodp i,j(1i,jp1,i<j),gimodp̸=gjmodp,那么 g g g p p p的原根
原根没什么快速求法,只能暴力枚举判断
通常模数常见的有 998244353 , 1004535809 , 469762049 998244353,1004535809,469762049 998244353,1004535809,469762049,这几个的原根都是 3 3 3
就这么少东西?好像真的只有这么少


NTT(快速数论变换)

F F T FFT FFT可以大大优化是因为 ω \omega ω有着神奇且优秀的性质
其实原根也有这种性质!

N T T NTT NTT里,我们可以拿原根来代替 F F T FFT FFT的单位根
具体就是,当合并区间的长度为 l e n = 2 m i d len=2mid len=2mid时,单位根为 cos ⁡ 2 π l e n + i sin ⁡ 2 π l e n = cos ⁡ π m i d + i sin ⁡ π m i d \cos{2\pi\over len}+i\sin{2\pi\over len}=\cos{\pi \over mid}+i\sin{\pi \over mid} coslen2π+isinlen2π=cosmidπ+isinmidπ
而原根即为 g p − 1 l e n = g p − 1 2 m i d g^{p-1\over len}=g^{p-1\over 2mid} glenp1=g2midp1
注意大多题目的模数 p = 998244353 p=998244353 p=998244353,此时 g = 3 g=3 g=3,即可代入原根计算 N T T NTT NTT
如果理解了 F F T FFT FFT的话, N T T NTT NTT也就迎刃而解了
时间复杂度 O ( n log ⁡ 2 n ) O(n\log_2n) O(nlog2n)


NTT板子

这些是定义

#define g 3//模数的原根
#define mod 998244353//通常情况下的模数

int pow(int x,int y)//快速幂
{
	ll z=1ll*x,ans=1ll;
	for (;y;y/=2,z=z*z%mod)if (y&1)ans=ans*z%mod;//注意精度
	return (int)ans%mod;
}

这个是 N T T NTT NTT板子,拿 F F T FFT FFT的改那么几下就好了

inline void ntt(int a[],int len,int inv)
{
	int bit=0;
	while ((1<<bit)<len)++bit;
	fo(i,0,len-1)
	{
		rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
		if (i<rev[i])swap(a[i],a[rev[i]]);
	}//前面和FFT一样
	for (int mid=1;mid<len;mid*=2)
	{
		int tmp=pow(g,(mod-1)/(mid*2));//原根代替单位根
		if (inv==-1)tmp=pow(tmp,mod-2);//逆变换则乘上逆元
		for (int i=0;i<len;i+=mid*2)
		{
			int omega=1;
			for (ll j=0;j<mid;++j,omega=omega*tmp%mod)
			{
				int x=a[i+j],y=omega*a[i+j+mid]%mod;
				a[i+j]=(x+y)%mod,a[i+j+mid]=(x-y+mod)%mod;//注意取模
			}
		}//大体和FFT差不多
	}
}

F F T FFT FFT一样, N T T NTT NTT也只能处理 n n n 2 2 2的次幂的多项式
N T T NTT NTT调用和 F F T FFT FFT一模一样,注意 l o n g l o n g long long longlong和除法都变成乘逆元
N T T NTT NTT变换后每一项系数乘上多项式长度的逆元即可


NTT没了…

本人版权意识薄弱……

其实 N T T NTT NTT还有更高级、更黑科技的用法
任意模数 N T T NTT NTT慢慢学…

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页