本文主要讲述了多项式的定义,系数表达及点值表达,两种表达方式的转换,优化多项式乘法的思路等问题。

主要是作为学习 FFT 的预备知识。

多项式

以下形式的 A(x) 称为「多项式」。

A(x)=n1i=0aixi

即多项式是若干个 x 的幂次与其对应系数乘积的和。

该多项式的最高项次数为 n1,将其称为多项式的「次数」,即 A(x) 可以称作次数为 n1 的多项式。

任何一个严格大于多项式次数的整数,称为多项式的「次数界」,即 A(x) 可以称作次数界为 n,n+1,n+2, 的多项式。

举个例子的话,A(x)=1+3x+3x2 是一个次数为 2,次数界大于 2 的多项式。

多项式的表达

一般来说,我们有「系数表达」和「点值表达」两种方法来表示一个多项式。

系数表达

即用系数来表达多项式,这也是我们平常所用的方法。

可以将多项式的系数看做一个 n 维(列)向量: a=(a0,a1,,an1)a 称作多项式的「系数表达」。

如上文中例子的系数表达即为 (1,3,3)

点值表达

通俗地说,点值表达就是取 n 个(互不相同的)数代进 A(x) 去求出值来,这样的得到 n 个点,即为多项式的「点值表达」。

用数学符号来表示: {(xi,yi)i[0,n),iZ} 其中 yi=A(xi)

如上文中例子的一个点值表达为 (0,1),(1,7),(2,19)

多项式的点值表达不是唯一的,因为你可以任意更换求值的点(但总应保证它们是互不相同的),但 n 个点的点值表达一定可以确定一个次数为 n1 的多项式(下文中会证明这一点)。

相互转换

系数表达 -> 点值表达

按照定义,我们取 n 个不同的数代入多项式求值即可,如果采用秦九韶算法(又称霍纳法则)进行单次求值,那么总的复杂度是 O(n2)

秦九韶算法,可以在 O(n) 的时间内对多项式单点求值,基于以下公式。

A(x0)=a0+x0(a1+x0(a2++x0(an2+x0an1)))

我们不妨把这个过程称之为多项式的「求值」。

点值表达 -> 系数表达

由点值表达到系数表达的转换,称为多项式的「插值」。

多项式的插值实际上描述了一个解线性方程组的过程,考虑以下矩阵方程。

[1x0x20xn101x1x21xn111xn1x2n1xn1n1][a0a1an1]=[y0y1yn1]

一个可行的方法是求出左边矩阵的逆矩阵,乘到右边去,就可以获得系数表达。

左边这种形式的矩阵称为「范德蒙德矩阵」,不妨记作 V(x0,x1,,xn1),那么有 a=V(x0,x1,,xn1)1y

但一个问题是,这个矩阵是否存在逆矩阵,或者说这个方程是否有且有唯一解,。

为了解决这个问题,我们考虑左边矩阵的行列式,由「范德蒙德矩阵」的性质,可以证明:

det

那么,若 x_i 互不相等,则以上行列式必不为零,也就是说该矩阵可逆,这也就证明了,n 个点的点值表达可以唯一确定一个次数为 n - 1 的多项式。

因为我们可以在 O(n^3) 的时间内完成矩阵求逆,所以我们得到了一个 O(n^3) 的插值算法。

更优越一点的方法是使用「拉格朗日插值法」,基于如下公式:

A(x) = \sum_{i = 0}^{n - 1}y_i \frac {\prod\limits_{j \not = i}(x - x_j)} {\prod\limits_{j \not = i}(x_i - x_j)}

可以看出,通过该公式构造的 A(x) 是满足要求的,对于任意给定的 k,将 x_k 代入,和式中只有 y_k 这一项不为零,且系数为 1

仍以上文中那个多项式的点值表达为例,{(0, 1), (1, 7), (2, 19)},根据公式,可以构造出:

A(x) = 1 \frac {(x - 1)(x - 2)} {(0 - 1)(0 - 2)} + 7 \frac {(x - 0)(x - 2)} {(1 - 0)(1 - 2)} + 19 \frac {(x - 0)(x - 1)} {(2 - 0)(2 - 1)}

读者不妨自行将 x = 0, 1, 2 代入上式以验证其正确性(与这个构造方法的精巧),该式的化简结果确为 A(x) = 1 + 3x + 3x^2

使用「拉格朗日插值法」的复杂度是 O(n^2) 的。

多项式的基本运算

我们为什么要花费这么大的力气在这两种表达形式中转换呢,显然这是因为这两种表达形式各有优点。

多项式的加减法运算较为简单,在系数表达下,就是对应的系数相加减,在点值表达下,就是对应求值点的式值相加减,复杂度均为 O(n),不过多叙述。

多项式乘法在系数表达下相对复杂,定义如下:

若有 A(x) = \sum_{i = 0}^{n - 1}a_ix^i \\ B(x) = \sum_{i = 0}^{n - 1}b_ix^i C(x) = A(x)B(x) 运算如下: C(x) = \sum_{i = 0}^{2n - 2}c_ix^i 其中 c_i = \sum_{k = 0}^ia_kb_{i - k}

按照定义进行,在系数表达下,计算多项式乘法的复杂度为 O(n^2)

然而,在点值表达下,多项式乘法依旧简单,只要把对应求值点的式值相乘即可,复杂度 O(n)。要注意的是,由于 C 的次数界是 2n 的,所以我们需要将 AB 的点值表达扩展到 2n 才可以。

为了快速地计算系数表达下的多项式乘法,我们采取这样的策略:

首先将系数表达转换为点值表达(求值),然后在点值表达下进行 O(n) 的多项式乘法,然后再从点值表达转换回系数表达(插值)。

事实上,行文至此,这个策略看上去并不能加速我们的计算,因为我们目前所提及的求值和插值均是 O(n^2) 的。

一种优化的思路是,不是任选 n个毫无关联的值来进行求值与插值,而是通过选取特定的值,利用它们之间的联系来降低复杂度。

快速傅里叶变换 (Fast Fourier Transform, FFT) 就是运用了这一思路,使用单位复数根作为求值点对多项式进行 离散傅里叶变换 (Discrete Fourier Transform, DFT) (求值)和 逆离散傅里叶变换 (Inverse Discrete Fourier Transform, IDFT)(插值),从而加速到 O(n \log n)

关于 FFT 的细节,在另一篇单独的文章中有具体的阐述,~~参见快速傅里叶变换 - 学习笔记。~~ 其实还没写完 QWQ。