给定两个多项式:

如何求

方法一:暴力展开(朴素方法)

  • 时间复杂度:,其中 是多项式的次数。
  • 虽然简单直观,但当 很大时(比如百万级),效率极低。

方法二:点值表示 + 插值(思路升级)

我们知道:一个 次多项式可以由任意 个不同点唯一确定。

如果我们能在若干点 上快速计算出:,那么乘积多项式在这些点上的值就是:

再通过插值得到 的系数形式即可!

但问题在于:求插值多项式的时间复杂度仍为 ,能否优化?

分治思想 —— 奇偶次项分离

考虑将多项式 按奇偶次幂分离:

其中:

  • (偶次项)
  • (奇次项)

计算在 处的值

只要计算一次 ,就能同时得到 两个点值

也可以用类似的思路继续划分

计算在 处的值:

我们知道 可以推出 的值,那么 有什么用呢?

观察

可知,一个 可以对应两个不同的 值(比如 ),使得它们的平方相同:

那么有

其中 ,表示 可以推出 的值

如果 呢?

如果你学过复数,会立马想到:

也就有

利用虚数单位 进行取值,由此我们可以得到四个点:

下面使用一个动画展示上述计算过程

(此处插入动画演示)

在动画中可以看到,我们找到了平方为 的两个数 和 平方为 的两个数 ,这看起来是个递归的过程

(插入动画单位根树状图)

根据欧拉公式 ,可以把这些值放到单位圆上,表示为

这些点被称为 次单位根

观察到

,单位圆每个数分别为

这些点还有一些性质

结合这些性质我们可以带入到公式

至此,可以使用 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]]