多项式算法
快速数论变换
了解快速傅里叶变换的人应该知道,快速傅里叶变换之所以能成立源于对任意正整数n,存在这样的一个单位根w(n),满足四个条件:
- w(n)0,w(n)1,…,w(n)n−1各不相同
- w(n)n=1
- w(dn)dk=w(n)k。
- w(2n)n=−1
在快速傅里叶变换中,我们选取w(n)=e(2π/n)i作为单位根。
快速傅里叶变换会存在一定的精度损失问题,我们可以使用快速数论变换来替代。但是快速数论变换实际上还是沿用了快速傅里叶变换的整体框架,只是替换了单位根。我们需要找一个素数p,并找到它的一个原根g。可以发现当我们取w(n)=gp−1n时,可以满足上面所有条件。不是所有p都适合用于快速数论变换,我们需要找一个比较特殊的p,使得n|p−1。由于n一般是2的幂次,因此我们希望p−1包含尽可能多的因子2,下面介绍两个常用的选择:
998244353=223⋅17⋅7+11004535809=221⋅479+1剩下的和快速傅里叶变换没有什么区别,直接套用就好了。
多项式求逆
给出多项式A,找到A的逆多项式B,满足AB=1(modxn)。
当n为1时,问题非常简单,此时A的逆多项式一定是1/A[0]。假设我们已经找到了某个多项式C,使得AC=1(modxn/2)。那么,可以推导出:
AC=AB=1(modxn/2)⇒A(B−C)=0(modxn/2)⇒(B−C)=0(modxn/2)⇒(B−C)2=0(modxn)最后一步需要说明下,对于B−C的后n项都是0,因此对于B−C的xi的系数,若i<n/2,那么系数一定是B−C的后n/2项中两项的乘积的累加,一定是0,若i≥n/2,那么一定是后n/2项和前n/2项的系数乘积的累和,也是0。
将二次方程展开:
(B−C)2=B2+C2−2BC=0(modxn)⇒B2=2BC−C2(modxn)之后两端都乘上A,借助AB=1可知:
AB2=2ABC−AC2(modxn)⇒B=2C−AC2(modxn)这样我们就得到了多项式B,并且可以发现,整个流程我们仅假设A[0]可逆,即一个多项式可逆当且仅当其x0项的系数可逆。
多项式求逆的时间复杂度为T(n)=T(n2)+αnlog2n=O(2αnlog2n)=O(nlog2n),其中α是固定常数。
多项式除法取模
对于阶数为n的多项式f(x),记fR(x)=xnf(1x),换句话说fR(x)是f(x)的系数前后交换后得到的。
接下来考虑阶数为n的多项式A和阶数为m的多项式B,以及阶数小于m的多项式R,我们希望得到如下形式等式A(x)=B(x)C(x)+R(x)。推一下公式:
A(x)=B(x)C(x)+R(x)⇒A(1x)=B(1x)C(1x)+R(1x)⇒xnA(1x)=(xmB(1x))(xn−mC(1x))+xnR(1x)⇒AR(x)=BR(x)CR(x)+xn−m+1RR(x)⇒AR(x)=BR(x)CR(x)(modxn−m+1)⇒CR(x)=AR(x)(BR(x))−1(modxn−m+1)最后的结果可以通过对多项式求逆得到。
而取模可以通过R(x)=A(x)−B(x)C(x)得到。
多项式除法和取模都仅涉及到一次的多项式求逆和乘法,因此时间复杂度为O(nlog2n)。
多项式多点求值
给出n阶多项式f,以及m个不同的数x1,…,xm,要求计算f(x1),…,f(xm)。
很显然可以设计一套暴力算法,O(nm)时间复杂度解决。但是这个问题存在丧心病狂的O(nlog2n+m(log2m)2)的解法。
我们可以m个数值分成相同大小的两部分。第一部分是X1=x1,…,xm2,第二部分是X2=xm2+1,…,xm。
之后我们定义g(x)=∏m2i=1(x−xi),很显然对于任意x′∈X1,满足g(x′)=0。我们利用多项式除法可以得到:f(x)=g(x)A(x)+R(x),其中R的阶数小于m2。利用x′∈X1⇒f(x′)=R(x′),因此我们现在只需要计算X1在某个不超过m阶的多项式下的取值。重复这个过程,直到点集不可再分。
分治的时间复杂度为:
T(m)=2T(m2)+αmlog2m=αm(log2m)2=O(m(log2m)2)。总的时间复杂度为O(nlog2n+m(log2m)2)。
提供一道题目。
等差卷积
考虑计算下面多项式:
H(x)=n∑i=0xin∑j=if(j)g(j−i)它非常类似于一般卷积,只是内部是等差,而一般卷积是等和。我们定义f′(x)=f(n−x)。那么代入得到
H′(x)=n∑i=0xii∑j=0f′(j)g(i−j)很显然H′可以利用一般的快速卷积在O(nlog2n)时间复杂度内得到,下面我们来看H′和H的关系。
H′i=∑ij=0f′(j)g(i−j)=∑ij=0f(n−j)g(i−j) 这里包含了所有差为n−i的项的和。因此可以得到:
H′i=Hn−i即最终我们可以通过翻转H′得到H。
注意这里我们要在f前面填充0之前翻转,且H′也仅翻转后面的n项目。
求序列等幂和
假设提供一个序列ai,记f(k)=∑ni=1aki,要求我们计算出f(1),f(2),…,f(m)。
这个问题可以用生成函数G(x)来求解。
G(x)=∑i=0n∑j=1aijxi=n∑j=1∑i=0(ajx)i=n∑j=111−ajx=∑nj=1∏j≠i(1−ajx)∏j(1−ajx)假如我们记D(x)=∏j(1−ajx),记U(x)=∑nj=1∏j≠i(1−ajx),那么我们可以发现Di和Ui存在某种联系。事实上,考虑所有对xj有贡献的系数一定是j个−ai序列中的数的乘积,而这个乘积会在Ui中出现贡献n−j次。因此我们可以直接断定Ui=(n−i)Di。
于是乎我们得到了U(x)和D(x)后,就可以用多项式求逆的技术得出G(x)=U(x)D−1(x)。
这里我们计算D(x)的时候,可以利用分治+快速卷积的技术将时间复杂度优化到O(n(log2n)2)。
多项式差与多项式前缀和
定义1:对于一个任意多项式f(x),我们记其前缀和函数为g(x)=∑xi=0f(i)。
命题1:对于任意n阶多项式A(x),我们始终可以找到另外一个n+1阶多项式B(x),满足B(x)−B(x−1)=A(x)
证明:
我们可以利用归纳法进行证明,当A(x)是0阶多项式的时候,即A(x)=c,那么我们可以得到B(x)=cx,很显然B(x)−B(x−1)=cx−c(x−1)=c=A(x)。
假设对于任意不大于k−1阶的多项式A(x),命题都成立,那么当A(x)为k阶多项式的时候,记A(x)=akxk+…+a0,注意到xk−(x−1)k是k−1阶多项式,因此我们可以通过为B(x)选择合适的最高项系数bk+1=akk,将问题化简为(B(x)−bk+1xk+1)−(B(x−1)−bk+1(x−1)k+1)=A(x)−(bk+1xk+1−bk+1(x−1)k+1),我们重新标记后得到公式:B′(x)−B′(x−1)=A′(x),其中我们要找的B′(x)是k阶多项式,而A′(x)是k−1阶多项式,而之前的归纳法已经证明了B′(x)的存在性,因此B(x)=B′(x)+bk+1xk+1也是存在的。
命题2:如果多项式f(x)是n阶多项式,那么其前缀和函数g(x)一定是n+1阶多项式
证明:
首先注意到g(x)−g(x−1)=f(x),按照命题1我们一定可以刻画出这样一个n+1阶多项式g(x),现在只需要保证g(0)=f(0)即可。注意到修改g(x)的常数项不会影响g(x)−g(x−1)=f(x)等式的成立,因此我们可以通过修改g(x)的常数项为f(0)即可找到与前缀和函数完全相同的一个n+1阶多项式。
Chirp-Z Transform
考虑这样一个问题:给定一个n阶多项式f(x)=∑ni=0aixi,以及某个数c和m,要求计算f(c0),f(c1),…,f(cm)。其中1≤n,m≤3×105。
暴力做法n2显然不可行。
可以发现
f(cj)=n∑i=0aicij由于ij=(i+j2)−(i2)−(j2),因此有
f(cj)=n∑i=0aicij=n∑i=0aic(i+j2)−(i2)−(j2)稍加转换可以得到:
f(cj)c(j2)=n∑i=0aic(i+j2)−(i2)S⇒f(cj)c(j2)=n∑i=0[c(i+j2)][aic−(i2)]可以发现右边是两个多项式的等差卷积,因此可以直接用快速卷积算法O((n+m)log2(n+m))快速求解。
提供一道题目,卡常题,慎做。
多元多项式卷积
非常神奇的技术,从这个博客学到的。
考虑给定两个k元多项式f(x1,…,xk)和g(x1,…,xk),求它们的卷积对(xn11,…,xnkk)取模的结果(注意这里取模并不是不进位的意思,而是舍弃发生进位的项的意思)。记N=∏ki=1ni
我们可以用一个0到N−1的下标来表示多项式中的一项。
考虑如何移除进位项带来的影响,我们可以引入一个新的哈希函数χ(i)=∑k−1j=1⌊i∏t≤jnt⌋。考虑到0≤⌊a+b∏t≤jnt⌋−⌊a∏t≤jnt⌋−⌊b∏t≤jnt⌋≤1,因此有0≤χ(a+b)−χ(a)−χ(b)<k,其中如果χ(a+b)−χ(a)−χ(b)=0,则表示a+b过程中不发生进位。
之后我们将f重新写作如下的形式f(x,z)=∑N−1i=0fixizχ(i),同理g(x,z)=∑N−1i=0gixizχ(i)。我们可以从它们中还原出原先的k元多项式。
考虑f和g中两项的乘积:
fixizχ(i)⋅gjxjzχ(j)=figjxi+jzχ(i)+χ(j)[χ(i+j)=χ(i)+χ(j)]其中它们的乘积会被统计到我们结果中,当且仅当χ(i)+χ(j)=χ(i+j)。由于0≤χ(a+b)−χ(a)−χ(b)<k以及χ(i)可能比较大,因此我们可以让结果对zk−1取模,可以发现信息是不会丢失的。
接下来我们只需要计算f∗g,并舍弃那些x项指数i与t项指数j不匹配的项,即χ(i)≠j,这些项是发生了进位的。
最后我们要考虑如何快速计算两个二元多项式的卷积。事实上我们可以按t的指数对两边进行展开,令Fi=∑N−1i=0fixi[χ(i)=t],Gi=∑N−1i=0gixi[χ(i)=t]:
f∗g=(N−1∑i=0fixizχ(i))∗(N−1∑i=0gixizχ(i))=(k−1∑t=0ztN−1∑i=0fixi[χ(i)=t])∗(k−1∑t=0ztN−1∑i=0gixi[χ(i)=t])=(k−1∑t=0ztFt)∗(k−1∑t=0ztGt)=k−1∑t=0k−1∑p=0zt+pFt∗Gp现在问题就变成了k2个多项式的乘积了,一种方案就是直接暴力FFT,时间复杂度为O(k2Nlog2N),当然我们可以先对每个Fi和Gi预处理它们的DFT形式,那么公式中卷积就可以简单的用点积来替代,之后把同类项合并,在一同取逆即可,时间复杂度可以减少到O(kNlog2N+k2N),考虑到k≤log2N,因此时间复杂度可以简写为O(kNlog2N)。
提供几道题目:
话说子集卷积其实也是多元多项式的卷积,对(x21,…,x2k)进行取模而已。所以同样可以用这个算法解子集卷积,不过由于FFT操作会慢于大部分FWT,因此最好还是用FWT做子集卷积。