你不可不读的多项式教程


写在前面

看懂这篇博客,需要掌握

  • 一般程度的代数知识
  • 入门程度的复数知识

多项式乘法与卷积

对于给定的两个多项式

\[F(x),G(x) \]

我们称

\[S(x)=\sum_k\sum_{i+j==k}F(i)G(j) \]

\(F,G\)的卷积。不难证明,这就是多项式乘法,即

\[F(x)G(x)=\sum_k\sum_{i+j==k}F(i)G(j) \]

(简单地说,\(F,G\)的乘积中次数为\(k\)的项系数为

\[\sum_{i+j==k}F(i)G(j) \]

计算\(F,G\)的卷积显然有\(O(n^2)\)的朴素思路,但是对于较高的数据范围这样的时间复杂度是无法接受的。


多项式的点值表达

除了常见的系数表达以外,多项式还可以用点值表达来表示。

对于已知次数为\(n\)的多项式\(F(x)\),我们可以用

\[X=\{(x_1,F(x_1)),(x_2,F(x_2),...,(x_{n+1},F(x_{n+1})))\} \]

来唯一确定其系数表达式。证明可以由代数基本定理推出。

多项式的点值表达相对于系数表达有一个优点:在计算卷积时时间复杂度缩减为\(O(n)\)。更准确地说,有

\[S(x)=\{(x_1x_2,F(x_1)G(x_2))...\}=F(x)G(x) \]

但是将系数表达转换为点值表达的过程仍然是\(O(n^2)\)的,于是我们想到对这个过程进行优化。


单位根

快速傅里叶变换能够在\(O(nlogn)\)的时间内解决系数表达到点值表达的过程,是因为引入了单位根的概念。称方程

\[z^n=1 \]

的复数根为\(n\)次单位根。这样的单位根一共有\(n\)个,记为

\[\omega^k_n=e^{\frac{2\pi ki}{n}}(k=0,1,...,n-1) \]

两条基本性质如下:

\[|\omega_n^k|=1 \tag{1} \]

\[\omega_n^x\omega_n^y=\omega_n^{x+y} \tag{2} \]

\((1)\)的证明是显然的:复数根在复平面上的表示即为单位圆的半径。

\((2)\)可以通过

\[\omega_n^k=cos\frac{2k\pi}{n}+sin\frac{2k\pi}{n}i \tag{*} \]

计算得出。本条性质还可以推出另外两条常用推论:

\[\omega_n^k=(\omega_n^1)^k \]

\[(\omega_n^k)^l=\omega_n^{kl} \]

此外,单位根还有两个重要的性质

\[\omega_n^k=\omega_{nl}^{kl} \tag{3} \]

\[\omega_n^{k+n/2}=-\omega_n^k \tag{4} \]

\((3)\)\((4)\)的证明都可以通过直接套用\((*)\)完成。


快速傅里叶变换(FFT)

快速傅里叶变换最核心的环节(之一)就是多项式的系数表达与点值表达的转换,可以采用分治的方式解完成。(此处先介绍递归写法)

对于多项式(为什么为最高次为\(n-1\)次下面解释)

\[F(x)=\sum_{i=0}^{n-1}a_ix^i (n=2^k) \]

\[F(x)=(a_0+a_2x^2+...+a_{n-2}x^{n-2})\\+(a_1x+a_3x^3+...+a_{n-1}x^{n-1}) \]

不妨设

\[L(x)=a_0+a_2x+...+a_nx^{\frac{n-2}{2}}\\ R(x)=a_1+a_3x+...+a_n-1x^{\frac{n-2}{2}} \]

那么有

\[F(x)=L(x^2)+xR(x^2) \]

(此时可以看出为什么此前将\(F\)最高次设为\(n-1\):美观)

现在我们要求\(F\)的点值表达,于是代入单位根,也就是

\[F(\omega_n^k)=L((\omega_n^k)^2)+\omega_n^kR((\omega_n^k)^2)\\ =L(\omega_{n/2}^k)+\omega_n^kR(\omega_{n/2}^k) \]

同理,可以得到

\[F(\omega_n^{k+n/2})=L((\omega_n^{k+n/2})^2)+\omega_n^{k+n/2}R((\omega_n^{k+n/2})^2)\\ =L(\omega_n^{2k+n})-\omega_n^kR(\omega_n^{2k+n})\\ =L(\omega_n^{2k})-\omega_n^kR(\omega_n^{2k})\\ =L(\omega_{n/2}^k)-\omega_n^kR(\omega_{n/2}^k) \]

也就是说,如果我们求出了\(L,R\)\(\omega_{n/2}^0,...,\omega_{n/2}^{n/2-1}\)的点值表达,我们就可以得到\(F\)\(\omega_{n}^0,...,\omega_{n}^n-1\)的点值表达。

由于每一次长度减半,所以可以在\(O(nlogn)\)的时间内求出多项式\(F\)的点值表示。

对于多项式的点值表达到系数表达的转换,可以推出类似的公式。

\(FFT\)的递归实现如下:

const double pi=acos(-1);
typedef complex<double> cx;
	
void fft (vector<cx> x,int n,int type) {
	if (n==1) return;
	vector<cx> l,r;
	for (register int i=0; i<n; i+=2) 
		l.push_back(x[i]),r.push_back(x[i+1]);
	fft(l,n>>1,type),fft(r,n>>1,type);
	cx wn(cos(2*pi/n),type*sin(2*pi/n)),w(1,0);
	for (register int i=0; i<(n>>1); ++i,w*=wn) {
		x[i]=l[i]+w*r[i],x[i+(n>>1)]=l[i]-w*r[i];
	}
}

在得到了递归实现以后,我们发现其常数较大,因此考虑是否存在非递归实现。观察递归过程中的数组下标,其变换过程如下

\[0~~~1~~~2~~~3~~~4~~~5~~~6~~~7\\0~~~2~~~4~~~6~|~1~~~3~~~5~~~7\\0~~~4~|~2~~~6~|~1~~~5~|~3~~~7\\0~|~4~|~2~|~6~|~1~|~5~|~3~|~7 \]

观察其二进制表示

\[000~~~001~~~010~~~011~~~100~~~101~~~110~~~111\\000~~~010~~~100~~~110~|~001~~~011~~~101~~~111\\000~~~100~|~010~~~110~|~001~~~101~|~011~~~111\\000~|~100~|~010~|~110~|~001~|~101~|~011~|~111 \]

不难发现每一项递归之后的最终位置为其二进制表示翻转的结果。

那么我们可以通过预处理一个\(R\)数组来直接得到最终位置。不难发现,每次递归实质上是按照对应位进行排序,于是我们可以得到实现:

for (register int i=0; i<n; ++i) {
	R[i]=(R[i>>1]>>1)|((i&1)<<(L-1));
}

那么根据\(R\),我们可以写出\(FFT\)的非递归实现:

void fft (vector<cx> x,int n,int type) {
	for (register int i=0; i<n; ++i)  // 求出递归后的数组
		if (i<R[i]) swap(x[i],x[R[i]]); // 如果不判断就会交换两次,等于没交换
	for (register int i=1; i<n; i<<=1) { // 枚举区间长度的一半(当然也可以枚举区间长度)
		cx wn(cos(pi/i),type*sin(pi/i)); // 此处消掉了公式里的2
		for (register int j=0; j<n; j+=(i<<1)) { // 枚举区间,对每一个区间做修改
			cx w(1,0);
			for (register int k=0; k<i; ++k,w*=wn) { // 枚举区间左半侧,同时修改右半侧
				cx t=x[j+k],s=x[j+k+i]*w;
				x[j+k]=t+s,x[j+k+i]=t-s;
			}
		}
	}
}

快速数论变换(NTT)

\(NTT\)\(FFT\)的唯一区别在于将单位根改为了原根,从而有效避免了复数取模运算中的精度丢失。原根满足此前提到的单位根的性质,其细节不做赘述。实现如下:

void ntt (vector<int> x,int n,int type) {
	for (register int i=0; i<n; ++i) 
		if (i<R[i]) swap(x[i],x[R[i]]);
	for (register int i=1; i<n; i<<=1) {
		int wn=ksm(type==1?G:invG,(MOD-1)/(i<<1));
		for (register int j=0; j<n; j+=(i<<1)) {
			int w=1;
			for (register int k=0; k<i; ++k,w*=wn) {
				int t=x[j+k],s=(x[j+k+i]*w)%MOD;
				x[j+k]=(t+s)%MOD,x[j+k+i]=(t-s)%MOD;
			}
		}
	}
}

\(G\)为模数的原根,\(invG\)为该原根在模数意义下的逆元。对于很大一部分题目(准确地说,对于满足\(MOD=a*2^b+1\)的题目),我们能够直接由暴力求出其原根,对于另一类任意模数题目,则需要利用中国剩余定理进行求解。暴力实现如下:

void getG () {
	int tmp=MOD;
	for (register int i=2; i*i<=MOD; ++i) {
		if (tmp%i==0) {
			fac[++cnt]=i;
			while (tmp%i==0) tmp/=i;
		}
	}
	if (tmp!=1) fac[++cnt]=tmp;
	for (register int i=2; i<MOD; ++i) {
		int flag=1;
		for (register int j=1; j<=cnt; ++j) {
			if (ksm(i,(MOD-1)/fac[j])==1) {
				flag=0;
				break;
			}
		}
		if (flag) {
			write(i);
			break;
		}
	}
}

任意模数NTT暂时先坑着,回头写完多项式求逆和开根再填。


多项式求逆

对于\(n-1\)次多项式\(F\),其逆元为满足

\[F(x)G(x)\equiv1(mod~x^n) \]

的多项式\(G\)

假设我们已经求出满足

\[F(x)G^{'}(x)\equiv1(mod~x^{\frac{n}{2}}) \]

\(G^{'}\),由于显然有

\[F(x)G(x)\equiv1(mod~x^n) \]

那么有

\[F(x)(G(x)-G^{'}(x))\equiv0(mod~x^{\frac{n}{2}}) \]

如果\(F(x)\equiv0(mod~x^{\frac{n}{2}})\),结果是显然的。那么考虑

\[G(x)-G^{'}(x)\equiv0(mod~x^{\frac{n}{2}}) \]

等式两侧同时平方,得到

\[G^2(x)+G^{'2}(x)-2G(x)G^{'}(x)\equiv0(mod~x^n) \]

两侧同乘\(A(x)\),有

\[G(x)+F(x)G^{'2}(x)-2F(x)G^{'}(x)\equiv0(mod~x^n) \]

所以

\[G(x)\equiv G^{'}(x)(2-F(x)G^{'}(x))(mod~x^n) \]

于是说明了我们可以用模\(x^{\frac{n}{2}}\)意义下逆元推出模\(x^n\)的意义下的逆元。

非递归的多项式求逆实现如下:

void inv (vector<int> x,vector<int> y,int n) {
	b[0]=ksm(a[0],MOD-2);
	int len;
	for (len=1; len<(n<<1); len<<=1) {
		for (register int i=0; i<len; ++i) A[i]=x[i],B[i]=y[i];
		for (register int i=0; i<len<<1; ++i) R[i]=(R[i>>1]>>1)|((i&1)?len:0);
		ntt(A,len<<1,1),ntt(B,len<<1,1);
		for (register int i=0; i<len<<1; ++i) y[i]=((2-A[i]*B[i]%MOD)*B[i]%MOD+MOD)%MOD;
		ntt(y,len<<1,1);
		for (register int i=len; i<len<<1; ++i) y[i]=0;
	}
	for (register int i=0; i<len; ++i) A[i]=B[i]=0;
	for (register int i=len; i<len<<1; ++i) y[i]=0;
}

多项式开根

对于\(n-1\)次多项式\(F\),其开根结果为满足

\[F(x)\equiv G^2(x)(mod~x^n) \]

的多项式\(G\)

假设我们已经求出满足

\[F(x)\equiv G^{'2}(x)(mod~x^{\frac{n}{2}}) \]

\(G^{'}\),由于显然有

\[F(x)\equiv G^2(x)(mod~x^n) \]

那么有

\[G^2(x)-G^{'2}(x)\equiv0(mod~x^{\frac{n}{2}}) \]

可以化简得到

\[(G(x)+G^{'}(x))(G(x)-G^{'}(x))\equiv0(mod~x^{\frac{n}{2}}) \]

所以

\[G(x)-G^{'}(x)\equiv0(mod~x^\frac{n}{2}) \]

两侧同时平方,得到

\[G^2(x)+G^{'2}(x)-2G(x)G^{'}(x)\equiv0(mod~x^n) \]

化简得

\[F(x)+G^{'2}(x)-2G(x)G^{'}(x)\equiv0(mod~x^n) \]

所以

\[G(x)\equiv \frac{F(x)+G^{'2}(x)}{2G^{'}(x)}(mod~x^n) \]

于是说明了我们可以用模\(x^{\frac{n}{2}}\)意义下开根结果推出模\(x^n\)的意义下的开根结果。

posted @ 2019-10-09 21:11  Ilverene  阅读(560)  评论(0编辑  收藏  举报