给定两个多项式:
如何求 ?
方法一:暴力展开(朴素方法)
- 时间复杂度:,其中 是多项式的次数。
- 虽然简单直观,但当 很大时(比如百万级),效率极低。
方法二:点值表示 + 插值(思路升级)
我们知道:一个 次多项式可以由任意 个不同点唯一确定。
如果我们能在若干点 上快速计算出: 和 ,那么乘积多项式在这些点上的值就是:
再通过插值得到 的系数形式即可!
但问题在于:求插值多项式的时间复杂度仍为 ,能否优化?
分治思想 —— 奇偶次项分离
考虑将多项式 按奇偶次幂分离:
其中:
- (偶次项)
- (奇次项)
计算在 和 处的值
只要计算一次 和 ,就能同时得到 和 两个点值
和 也可以用类似的思路继续划分
计算在 和 处的值:
我们知道 和 可以推出 和 的值,那么 和 有什么用呢?
观察
可知,一个 可以对应两个不同的 值(比如 和 ),使得它们的平方相同:
那么有
其中 时 ,表示 和 可以推出 和 的值
如果 呢?
如果你学过复数,会立马想到:
也就有
利用虚数单位 进行取值,由此我们可以得到四个点:
下面使用一个动画展示上述计算过程
(此处插入动画演示)
在动画中可以看到,我们找到了平方为 的两个数 和 和 平方为 的两个数 和 ,这看起来是个递归的过程
(插入动画单位根树状图)
根据欧拉公式 ,可以把这些值放到单位圆上,表示为
这些点被称为 次单位根
观察到
令 ,单位圆每个数分别为
这些点还有一些性质
结合这些性质我们可以带入到公式 中
至此,可以使用 python 实现 FFT 计算过程
def FFT(P):
n = len(P)
if n == 1:
return P
Pe = P[::2]
Po = P[1::2]
Fe = FFT(Pe)
Fo = FFT(Po)
wn = np.exp(2 * np.pi / n * 1j)
F = [0] * n
for k in range(n // 2):
F[k] = Fe[k] + wn**k * Fo[k]
F[k + n // 2] = Fe[k] - wn**k * Fo[k]
return F回到正题,我们已经得到了 个插值点,现在如何还原为多项式呢?
将这些单位根 带入
也就是
这是傅里叶变换
的离散形式,称为离散傅里叶变换(Discrete Fourier transform,DFT),这也是本算法称为快速傅立叶变换(Fast Fourier Transform,FFT)原因
其中
是范德蒙德矩阵,取逆矩阵可得
只需将单位元 取逆(倒数) ,即可得到 FFT 逆变换
def FFT(P, inv=1): # inv=-1表示逆变换
n = len(P)
if len(P) == 1:
return P
Pe = P[::2]
Po = P[1::2]
Fe = FFT(Pe, inv)
Fo = FFT(Po, inv)
wn = np.exp(inv * 2 * np.pi / n * 1j)
F = [0] * n
for k in range(n // 2):
F[k] = Fe[k] + wn**k * Fo[k]
F[k + n // 2] = Fe[k] - wn**k * Fo[k]
return F
def IFFT(P: list):
n = len(P)
F = FFT(P, -1)
return [x / n for x in F]回到最开始的问题,如何求解两个多项式的乘积
def multiply(A, B):
n = len(A) + len(B) - 1
nn = 2 << int(math.log2(n - 1))
f = FFT(A + [0j] * (nn - len(A)))
g = FFT(B + [0j] * (nn - len(B)))
h = [x * y for x, y in zip(f, g)]
S = IFFT(h)
return [int(x.real + 0.49) for x in S[:n]]