NTT学习笔记

FFT NTT TNT 实现爆破的伟大之路。

FFT容易爆精度 这大家都知道所以在卷积之后要+0.5然后int强制转换。

有的时候题目中可以取模 但是我们FFT中带入的x是浮点型的 无法支持取模操作。

我们知道 取模是数论的内容。 那么 ~~老爹说过要用数论打败数论~~

代替单位复根的就是原根了。所谓原根就是设m是正整数,a是整数,若a模m的阶等于φ(m),则称a为模m的一个原根。

什么叫阶 a和模数m互质 使得 $a^dequiv 1(mod m)$成立的最小正整数d叫做a对模m的阶.

那么性质,a是阶的话对于正整数$1leq pleq m-1$有$a^p(mod p)$两两不相同 且当p为m-1时值为1.这牵扯到完全剩余系的问题了...

原根的性质 对于m是质数的时候 若g是m的原根 那么存在$g^{m-1}$=1(modp) ~~当然同余号太难打这里写等号~~

可以$w_n$把$g^{(p-1)/n}$看成是等价的

所以说这种质数必须是NTT质数(费马质数),即(P-1)有超过序列长度的2的正整数幂因子的质数,如一般这种质数都是998244353,1004535809,469762049等。

不是这种质数怎么办?找几个找乘积大于p^2*n的费马质数做,用中国剩余定理合并就好了。

为什么998244353是NTT的常用模数呢?NTT到底和单位根的本质上的联系是什么呢?

拿起计算器可以算一下 $998244353-1$ 之后大约是一个 $119 cdot 2^{23}=998244352$. 很神奇对吧 这又意味着什么呢。

考虑我们的单位根 一开始我们选取2的若干次幂的底数x 每个东西是什么$e^{frac{2π}{2^w}}$这个东西就是我们的自变量了。

为什么是这个我就不再赘述 一些消去原理和折半原理 以及 单位根的特性然后实现FFT

考虑有模数的限制的话我们就不能再选取单位根了 因为那是虚数和小数的夹杂。

先来点简单的如模数就是$998244353$这个NTT常用模数的时候这个模数我简记为mod,我们选取的x 首先 数量要大于n+m+1。

然后我们想要折半降低复杂度的话,原根的一个特点 设g为$mod$的原根 那么存在$g^{mod-1}$%mod=1;

且 原根的完全剩余系为[1,mod-1] 当然关于这方面的知识我也不太清楚 不能再口胡下去了。

我们的x的选取 即是$g^{frac{mod-1}{2^k}}$ 这样我们考虑一下折半原理的使用

在我们分治之后 设P(x) 表示在当前这层我们要计算的一个值 k是当前的次数 (因为毕竟是一个递归的过程

p(x)=$a_0+a_1x+a_2x^2+...a_kx^{k-1}$ 这样的话我们设两个式子

$A_0$(x)=$a_0+a_2x+a_4x^2+...a_{k-2}x^{frac{k}{2}-1}$

$A_1$(x)=$a_1+a_3x+a_5x^2+...a_{k-1}x^{frac{k}{2}-1}$

那么p(x)=$A_0(x^2)$+$xA_1(x^2)$ 我们先前有了 一个折半原理 是这样的 先说单位根吧。

$w_n^k=e^{frac{2kπi}{n}}$ 我们给个平方 $(w_n^k)^2=(e^{frac{2kπi}{n}})^2=w_{frac{n}{2}}^k$ 挺显然的说实话。

然后之所以能折半是因为 考虑一下当$kleqfrac{n}{2}-1$ $(w_n^{k+frac{n}{2}})^2=(w_n^kcdot w_n^n)^2$ 由单位根的定义可知$w_n^n$=1;

那么我们得到的那个等式 $(w_n^{k+frac{n}{2}})^2=(w_n^k)^2$ 而$w_n^k$的平方也是$(w_n^k)^2$

所以说我们带入$x^2$的时候 有一半是不用计算的 他们的平方是相等的 且我们计算出来他们的平方然后分别处理他们即可。这样就可以完成折半了。

接下来引导我们的原根上面 刚才我们提到了我们的x是$g^{frac{k*(mod-1)}{2^n}}$

考虑一下 还是考虑较少的一半 $kleqfrac{n}{2}-1$的时候 首先先求一下$(g^{frac{k(mod-1)}{n}})^2=?$

由于本人才疏学浅以下皆为口胡.

接下来考虑另外一半 $g^{frac{(k+n)cdot{(mod-1)}}{n}}$=$g^{frac{kcdot(mod-1)}{n}}$ 证明不难。

所以就是这样就做好了折半也是可以折了 然后 没了 就这样搞 至于 为什么是998244353这个模数为常用模数

考虑我们折半的时候 每折一次 我们都会变成2的若干次方998244352这个数字刚好能折23次 也就是说可以胜任8388608这么大的多项式的卷积

考虑不能这么搞怎么办 将模数分解成若干$xcdot{2^k}+1$的质数的乘积 最后利用中国剩余定理进行合并即可。

的确毒瘤我还需要好好的思考。

最后再点一下插值的计算。

插值:根据已知数据点(条件),来预测未知数据点值得方法。假如你有n个已知条件,就可以求一个n-1次的插值函数P(x),使得P(x)接近未知原函数f(x),并由插值函数预测出你需要的未知点值。更简单的定义:根据点值求函数.

我们 还是够早我们 原矩阵的逆矩阵再套用FFT计算即可.注意 带入的x变成逆元。

给出一道模板题目吧。[多项式乘法求逆](https://www.luogu.com.cn/problem/P4238)

这年头NTT模板图模数给的不太正常可以用FFT来写 这里还是来一发标准的NTT吧。

求$F(x)cdot G(x)equiv 1(mod x^n)$ 然后正确的推导过程如下:

设$F(x)cdot H(x)equiv 1(mod x^{frac{n}{2}}) ag {1}$

也有$F(x)cdot G(x)equiv 1(mod x^{frac{n}{2}}) ag {2}$

由(2)-(1) 可以得到 $F(x)(G(x)-H(x))equiv 0(mod x^{frac{n}{2}})$

所以可以得到$G(x)-H(x)equiv 0(mod x^{frac{n}{2}})$ 关于这个等式的理解是

由第一个条件可知$F(x)$之中必然含有一个不等于0的常数项 注意这道题的$mod=998244353$.

这样的话 $G(x)-H(x)$的常数项必然为0或者在对$mod$取模的意义下为0

我们再来贯穿$G(x)-H(x)$之中最小的$x^r$的幂这个东西乘上$F(x)$常数必然不能被模掉或者被消掉因为$G(x)-H(x)$的常数项为0 所以没有$x^r$这一项所以归纳来思考$G(x)-H(x)equiv 0(mod x^{frac{n}{2}})$

把刚刚推得的式子平方$(G(x)-H(x))^2equiv 0(mod x^n)$ 再展开$G(x)^2-2G(x)H(x)+H(x)^2equiv 0(mod x^n)$

等式两边同乘以$F(x)$得到$G(x)-2H(x)+F(x)H(x)^2equiv 0(mod x^n)$

那么$G(x)equiv 2H(x)-F(x)H(x)^2 (mod x^n)$ 我们发现只需要求H(x)就可以了 成功把问题缩小一半直到缩小到1的时候我们直接求一个逆元即可。

//#include<bitsstdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define ld long double
#define put(x) printf("%d
",x)
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define EPS 1e-8
#define ui unsigned int
#define pb push_back
#define RI register int
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
	return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
	int x=0,f=1;char ch=getc();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
	return x*f;
}
const int MAXN=300010,mod=998244353,G=3;
int n;
int a[MAXN],b[MAXN],c[MAXN],rev[MAXN];
inline ll ksm(ll b,ll p)
{
	ll cnt=1;
	while(p)
	{
		if(p&1)cnt=cnt*b%mod;
		b=b*b%mod;
		p=p>>1;
	}
	return cnt;
}
inline void NTT(int *a,int n,int f)
{
	for(int i=0;i<n;++i)if(i<rev[i])swap(a[i],a[rev[i]]);
	for(int len=2;len<=n;len=len<<1)//当前的区间长度
	{
		int mid=len>>1;
		int gn=ksm(G,(mod-1)/len);
		if(f==-1)gn=ksm(gn,mod-2);
		for(int j=0;j<n;j+=len)
		{
			int g=1;
			for(int i=0;i<mid;++i)
			{
				int x=a[j+i],y=(ll)g*a[j+i+mid]%mod;
				a[j+i]=(x+y)%mod;a[j+i+mid]=(x-y+mod)%mod;
				g=1ll*g*gn%mod;
			}
		}
	}
	if(f==1)return;
	int w=ksm(n,mod-2);
	for(int i=0;i<n;++i)a[i]=(ll)a[i]*w%mod;
}
inline void sol(int n,int *a,int *b)
{
	if(n==1){b[0]=ksm(a[0],mod-2);return;}
	sol((n+1)>>1,a,b);
	int len=0,s=1;
	while(s<(n<<1))s=s<<1,++len;//求a的多项式乘b的多项式的点积
	for(int i=1;i<s;++i)rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));
	for(int i=0;i<n;++i)c[i]=a[i];
	for(int i=n;i<s;++i)c[i]=0;
	NTT(c,s,1);NTT(b,s,1);
	for(int i=0;i<s;++i)b[i]=(2-1ll*c[i]*b[i]%mod+mod)%mod*b[i]%mod;//求点积
	NTT(b,s,-1);
	for(int i=n;i<s;++i)b[i]=0;//取一半即可 传到上一层的是当前的一半即可.
}
int main()
{
	//freopen("1.in","r",stdin);
	n=read();
	for(int i=0;i<n;++i)a[i]=read();
	sol(n,a,b);
	for(int i=0;i<n;++i)printf("%d ",b[i]);
	return 0;
}
原文地址:https://www.cnblogs.com/chdy/p/12147194.html