0%

多项式全家桶

多项式全家桶


多项式乘法

多项式乘法$CX=AX\times BX$中的系数满足加法卷积$c_i=\Sigma a_j b_k [i==j+k]$,可以将一个乘法看作卷积,反之也是。

一般来说,多项式直接相乘的时间复杂度都是$O(n^2)$级别的,使用压位等技巧也只能做到常数上的优化,面对更大的数据规模就捉襟见肘了。

既然是卷积,就可以考虑卷积通用的方法进行优化。如果能找到映射$f$,使得在$f$下点乘的性质成立,并且可求逆运算,即

就有可能找到更好的做法。


快速傅里叶变换

傅里叶变换是一个物理上的方法,虽然他们名字相同,但是不明白他和这个过程有什么使用上的联系。得知复数单位根有着优良的性质,自然想到要用他来优化多项式乘法

对于多项式

可以把他进行奇偶分解,记奇偶子式分别为$h(x),g(x)$

代入$x_i=\omega_n^i$,

这个求$f$的过程是典型的折半递归,得到了一个时间复杂度为$O(nlogn)$的求解法。求出了映射$A\rightarrow f(A)$,可以在线性的时间内实现点乘,接下来考虑逆变换$f(C)\rightarrow C$。

如果把dft过程看作一个矩阵乘法,那么idft就是矩阵的逆。其实用一个更好理解的想法,再次代入$\omega^{-1}$就逆映射。需要注意,直接代入求得的并不是原序列,由线代的知识可知,$A^{-1}=\frac{A^*}{|A|}$,需要再除以序列的长度n。

代码:


迭代fft

考虑在$f(a_i)$中所有有贡献的元素,可以发现他们的下标都等于$i>>k$。在$f(\omega)=g(\omega^2)+\omega h(\omega^2)$划分递归的过程,x也在作二进制下的右移。

如果能提前得到在$f(a_i)$所涉及的位置,就不需要递归,可以剩下栈开销与优化常数。

自底而顶地合并元素,可以发现当$i=rev[i]$时,满足fft的性质,其中

代码:


快速数论变换

fft使用了复数,有运行常数和精度的劣势,而且许多题目要对998244353取模,一个基于整数的变换算法会更合适。

由数论知识可知,对某些质数$P$,存在原根$g$,使得

那么就可以利用类似于上述的过程来得到变换

代码:



多项式运算

求导,积分

对于一个多项式函数,求导和积分的原理都非常简单。在计算机中的实现就是移动项并变换相应系数。
在ntt的做法中,可以预处理阶乘和逆阶乘。

代码:


求逆

$F(x)$的多项式逆元定义为$F(x)G(x)\equiv 1 (mod x^n)$。这个逆元只针对原多项式长度范围内的部分,对于乘积超出的部分直接忽略。

考虑递归求解。在递归边界$n=1$,$G_0=f_0^{-1}$。

在模$x^{\frac{n}{2}}$的意义下,定义$F(X)$的逆多项式$H(x)$。$H(x)$通过递归逐次求解。

可以看出,在低位$x^{\frac{n}{2}}$以下,$H(X)=G(X)$,即$H(X)$就是$G(X)$的低位部分。至于高位部分,考虑平方

这样,通过$\frac{n}{2}$项式$H(X)$,就可以得到$n$项式$G(X)$。

根据fft的经验,二进制分治的递归可以改写成迭代,求逆也可以改用迭代,这样时空开销更小。

代码:


开根

$F(x)$的根式定义为$G^2(x)\equiv F(x) (mod x^n)$。按照求逆的套路,考虑分治合并。

当$H(x)$是$F(x)$在一半长度下的根式

递归,求$H(x)$的逆元,就可由半长根式得全长根式

代码: