本文主要讲述了多项式的定义,系数表达及点值表达,两种表达方式的转换,优化多项式乘法的思路等问题。
主要是作为学习 FFT 的预备知识。
多项式
以下形式的 A(x) 称为「多项式」。
A(x)=n−1∑i=0aixi
即多项式是若干个 x 的幂次与其对应系数乘积的和。
该多项式的最高项次数为 n−1,将其称为多项式的「次数」,即 A(x) 可以称作次数为 n−1 的多项式。
任何一个严格大于多项式次数的整数,称为多项式的「次数界」,即 A(x) 可以称作次数界为 n,n+1,n+2,⋯ 的多项式。
举个例子的话,A(x)=1+3x+3x2 是一个次数为 2,次数界大于 2 的多项式。
多项式的表达
一般来说,我们有「系数表达」和「点值表达」两种方法来表示一个多项式。
系数表达
即用系数来表达多项式,这也是我们平常所用的方法。
可以将多项式的系数看做一个 n 维(列)向量: →a=(a0,a1,⋯,an−1) 则 →a 称作多项式的「系数表达」。
如上文中例子的系数表达即为 (1,3,3)。
点值表达
通俗地说,点值表达就是取 n 个(互不相同的)数代进 A(x) 去求出值来,这样的得到 n 个点,即为多项式的「点值表达」。
用数学符号来表示: {(xi,yi)∣i∈[0,n),i∈Z} 其中 yi=A(xi)。
如上文中例子的一个点值表达为 (0,1),(1,7),(2,19)。
多项式的点值表达不是唯一的,因为你可以任意更换求值的点(但总应保证它们是互不相同的),但 n 个点的点值表达一定可以确定一个次数为 n−1 的多项式(下文中会证明这一点)。
相互转换
系数表达 -> 点值表达
按照定义,我们取 n 个不同的数代入多项式求值即可,如果采用秦九韶算法(又称霍纳法则)进行单次求值,那么总的复杂度是 O(n2)。
秦九韶算法,可以在 O(n) 的时间内对多项式单点求值,基于以下公式。
A(x0)=a0+x0(a1+x0(a2+⋯+x0(an−2+x0an−1)…))
我们不妨把这个过程称之为多项式的「求值」。
点值表达 -> 系数表达
由点值表达到系数表达的转换,称为多项式的「插值」。
多项式的插值实际上描述了一个解线性方程组的过程,考虑以下矩阵方程。
[1x0x20⋯xn−101x1x21⋯xn−11⋮⋮⋮⋱⋮1xn−1x2n−1⋯xn−1n−1][a0a1⋮an−1]=[y0y1⋮yn−1]
一个可行的方法是求出左边矩阵的逆矩阵,乘到右边去,就可以获得系数表达。
左边这种形式的矩阵称为「范德蒙德矩阵」,不妨记作 V(x0,x1,⋯,xn−1),那么有 →a=V(x0,x1,⋯,xn−1)−1→y。
但一个问题是,这个矩阵是否存在逆矩阵,或者说这个方程是否有且有唯一解,。
为了解决这个问题,我们考虑左边矩阵的行列式,由「范德蒙德矩阵」的性质,可以证明:
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 的,所以我们需要将 A 和 B 的点值表达扩展到 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。