多项式算法

Published: by Creative Commons Licence

  • Tags:

快速数论变换

了解快速傅里叶变换的人应该知道,快速傅里叶变换之所以能成立源于对任意正整数n,存在这样的一个单位根w(n),满足四个条件:

  1. w(n)0,w(n)1,,w(n)n1各不相同
  2. w(n)n=1
  3. w(dn)dk=w(n)k
  4. w(2n)n=1

在快速傅里叶变换中,我们选取w(n)=e(2π/n)i作为单位根。

快速傅里叶变换会存在一定的精度损失问题,我们可以使用快速数论变换来替代。但是快速数论变换实际上还是沿用了快速傅里叶变换的整体框架,只是替换了单位根。我们需要找一个素数p,并找到它的一个原根g。可以发现当我们取w(n)=gp1n时,可以满足上面所有条件。不是所有p都适合用于快速数论变换,我们需要找一个比较特殊的p,使得n|p1。由于n一般是2的幂次,因此我们希望p1包含尽可能多的因子2,下面介绍两个常用的选择:

998244353=223177+11004535809=221479+1

剩下的和快速傅里叶变换没有什么区别,直接套用就好了。

多项式求逆

给出多项式A,找到A的逆多项式B,满足AB=1(modxn)

当n为1时,问题非常简单,此时A的逆多项式一定是1/A[0]。假设我们已经找到了某个多项式C,使得AC=1(modxn/2)。那么,可以推导出:

AC=AB=1(modxn/2)A(BC)=0(modxn/2)(BC)=0(modxn/2)(BC)2=0(modxn)

最后一步需要说明下,对于BC的后n项都是0,因此对于BCxi的系数,若i<n/2,那么系数一定是BC的后n/2项中两项的乘积的累加,一定是0,若in/2,那么一定是后n/2项和前n/2项的系数乘积的累和,也是0。

将二次方程展开:

(BC)2=B2+C22BC=0(modxn)B2=2BCC2(modxn)

之后两端都乘上A,借助AB=1可知:

AB2=2ABCAC2(modxn)B=2CAC2(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))(xnmC(1x))+xnR(1x)AR(x)=BR(x)CR(x)+xnm+1RR(x)AR(x)=BR(x)CR(x)(modxnm+1)CR(x)=AR(x)(BR(x))1(modxnm+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(xxi),很显然对于任意xX1,满足g(x)=0。我们利用多项式除法可以得到:f(x)=g(x)A(x)+R(x),其中R的阶数小于m2。利用xX1f(x)=R(x),因此我们现在只需要计算X1在某个不超过m阶的多项式下的取值。重复这个过程,直到点集不可再分。

分治的时间复杂度为:

T(m)=2T(m2)+αmlog2m=αm(log2m)2=O(m(log2m)2)

总的时间复杂度为O(nlog2n+m(log2m)2)

提供一道题目

等差卷积

考虑计算下面多项式:

H(x)=ni=0xinj=if(j)g(ji)

它非常类似于一般卷积,只是内部是等差,而一般卷积是等和。我们定义f(x)=f(nx)。那么代入得到

H(x)=ni=0xiij=0f(j)g(ij)

很显然H可以利用一般的快速卷积在O(nlog2n)时间复杂度内得到,下面我们来看HH的关系。

Hi=ij=0f(j)g(ij)=ij=0f(nj)g(ij) 这里包含了所有差为ni的项的和。因此可以得到:

Hi=Hni

即最终我们可以通过翻转H得到H

注意这里我们要在f前面填充0之前翻转,且H也仅翻转后面的n项目。

求序列等幂和

假设提供一个序列ai,记f(k)=ni=1aki,要求我们计算出f(1),f(2),,f(m)

这个问题可以用生成函数G(x)来求解。

G(x)=i=0nj=1aijxi=nj=1i=0(ajx)i=nj=111ajx=nj=1ji(1ajx)j(1ajx)

假如我们记D(x)=j(1ajx),记U(x)=nj=1ji(1ajx),那么我们可以发现DiUi存在某种联系。事实上,考虑所有对xj有贡献的系数一定是jai序列中的数的乘积,而这个乘积会在Ui中出现贡献nj次。因此我们可以直接断定Ui=(ni)Di

于是乎我们得到了U(x)D(x)后,就可以用多项式求逆的技术得出G(x)=U(x)D1(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(x1)=A(x)

证明:

我们可以利用归纳法进行证明,当A(x)是0阶多项式的时候,即A(x)=c,那么我们可以得到B(x)=cx,很显然B(x)B(x1)=cxc(x1)=c=A(x)

假设对于任意不大于k1阶的多项式A(x),命题都成立,那么当A(x)k阶多项式的时候,记A(x)=akxk++a0,注意到xk(x1)kk1阶多项式,因此我们可以通过为B(x)选择合适的最高项系数bk+1=akk,将问题化简为(B(x)bk+1xk+1)(B(x1)bk+1(x1)k+1)=A(x)(bk+1xk+1bk+1(x1)k+1),我们重新标记后得到公式:B(x)B(x1)=A(x),其中我们要找的B(x)k阶多项式,而A(x)k1阶多项式,而之前的归纳法已经证明了B(x)的存在性,因此B(x)=B(x)+bk+1xk+1也是存在的。

命题2:如果多项式f(x)n阶多项式,那么其前缀和函数g(x)一定是n+1阶多项式

证明:

首先注意到g(x)g(x1)=f(x),按照命题1我们一定可以刻画出这样一个n+1阶多项式g(x),现在只需要保证g(0)=f(0)即可。注意到修改g(x)的常数项不会影响g(x)g(x1)=f(x)等式的成立,因此我们可以通过修改g(x)的常数项为f(0)即可找到与前缀和函数完全相同的一个n+1阶多项式。

Chirp-Z Transform

考虑这样一个问题:给定一个n阶多项式f(x)=ni=0aixi,以及某个数cm,要求计算f(c0),f(c1),,f(cm)。其中1n,m3×105

暴力做法n2显然不可行。

可以发现

f(cj)=ni=0aicij

由于ij=(i+j2)(i2)(j2),因此有

f(cj)=ni=0aicij=ni=0aic(i+j2)(i2)(j2)

稍加转换可以得到:

f(cj)c(j2)=ni=0aic(i+j2)(i2)Sf(cj)c(j2)=ni=0[c(i+j2)][aic(i2)]

可以发现右边是两个多项式的等差卷积,因此可以直接用快速卷积算法O((n+m)log2(n+m))快速求解。

提供一道题目,卡常题,慎做。

多元多项式卷积

非常神奇的技术,从这个博客学到的。

考虑给定两个k元多项式f(x1,,xk)g(x1,,xk),求它们的卷积对(xn11,,xnkk)取模的结果(注意这里取模并不是不进位的意思,而是舍弃发生进位的项的意思)。记N=ki=1ni

我们可以用一个0N1的下标来表示多项式中的一项。

考虑如何移除进位项带来的影响,我们可以引入一个新的哈希函数χ(i)=k1j=1itjnt。考虑到0a+btjntatjntbtjnt1,因此有0χ(a+b)χ(a)χ(b)<k,其中如果χ(a+b)χ(a)χ(b)=0,则表示a+b过程中不发生进位。

之后我们将f重新写作如下的形式f(x,z)=N1i=0fixizχ(i),同理g(x,z)=N1i=0gixizχ(i)。我们可以从它们中还原出原先的k元多项式。

考虑fg中两项的乘积:

fixizχ(i)gjxjzχ(j)=figjxi+jzχ(i)+χ(j)[χ(i+j)=χ(i)+χ(j)]

其中它们的乘积会被统计到我们结果中,当且仅当χ(i)+χ(j)=χ(i+j)。由于0χ(a+b)χ(a)χ(b)<k以及χ(i)可能比较大,因此我们可以让结果对zk1取模,可以发现信息是不会丢失的。

接下来我们只需要计算fg,并舍弃那些x项指数it项指数j不匹配的项,即χ(i)j,这些项是发生了进位的。

最后我们要考虑如何快速计算两个二元多项式的卷积。事实上我们可以按t的指数对两边进行展开,令Fi=N1i=0fixi[χ(i)=t]Gi=N1i=0gixi[χ(i)=t]

fg=(N1i=0fixizχ(i))(N1i=0gixizχ(i))=(k1t=0ztN1i=0fixi[χ(i)=t])(k1t=0ztN1i=0gixi[χ(i)=t])=(k1t=0ztFt)(k1t=0ztGt)=k1t=0k1p=0zt+pFtGp

现在问题就变成了k2个多项式的乘积了,一种方案就是直接暴力FFT,时间复杂度为O(k2Nlog2N),当然我们可以先对每个FiGi预处理它们的DFT形式,那么公式中卷积就可以简单的用点积来替代,之后把同类项合并,在一同取逆即可,时间复杂度可以减少到O(kNlog2N+k2N),考虑到klog2N,因此时间复杂度可以简写为O(kNlog2N)

提供几道题目:

话说子集卷积其实也是多元多项式的卷积,对(x21,,x2k)进行取模而已。所以同样可以用这个算法解子集卷积,不过由于FFT操作会慢于大部分FWT,因此最好还是用FWT做子集卷积。

参考资料