简单来说,形如 $a_0+a_1X+a_2X^2+\cdots+a_nX^n$ 的代数表达式叫做
多项式
,可以记作$P(X)=a_0+a_1X+a_2X^2+\cdots+a_nX^n$,$a_0, a_1, \cdots, a_n$ 叫做多项式的
系数
,$X$ 是一个不定元,不表示任何值,不定元在多项式中最大项的次数称作多项式的
次数
多项式的系数表示法
像刚刚我们提到的那些多项式,都是以系数形式表示的,也就是将 $n$ 次多项式 $A(x)$ 的系数 $a_0, a_1, \cdots, a_n$ 看作 $n+1$ 维向量 $\vec a=(a_0,a_1,\cdots,a_n)$,其
系数表示
(coefficient representation)就是向量 $\vec a$
多项式的点值表示法
如果选取 $n+1$ 个
不同
的数 $x_0, x_1, \cdots, x_n$ 对多项式进行求值,得到 $A(x_0), A(x_1), \cdots, A(x_n)$,那么就称 ${\left(x_i, A(x_i)\right) : 0 \leq i \leq n, i \in \mathbb Z} $ 为多项式 $A(x)$ 的
点值表示
(point-value representation)
多项式 $A(x)$ 的点值表示不止一种,你只要选取不同的数就可以得到不同的点值表示,但是任何一种点值表示都能唯一确定一个多项式,为了从点值表示转换成系数表示,可以直接通过
插值
的方法
后面提到的 $i$,除非作为 $\sum$ 求和的变量,其余的都表示虚数单位 $\sqrt{-1}$
$n$ 次单位根是指能够满足方程 $z^n=1$ 的复数,这些复数一共有 $n$ 个它们都分布在复平面的单位圆上,并且构成一个正 $n$ 边形,它们把单位圆等分成 $n$ 个部分
根据复数乘法相当于模长相乘,幅角相加就可以知道,$n$ 次单位根的模长一定是 $1$,幅角的 $n$ 倍是 $0$
这样,$n$ 次单位根也就是
\[e^{\frac{2\pi ki}{n}}, k = 0, 1, 2, \cdots, n - 1\]
再根据欧拉公式
\[e^{\theta i}=\cos\theta + i\sin\theta\]
就可以知道 $n$ 次单位根的算术表示
如果记 $\omega_n=e^{\frac{2\pi i}{n}}$,那么 $n$ 次单位根就是 $\omega_n^0, \omega_n^1, \cdots, \omega_n^{n-1} $
多项式的乘法
给定两个多项式 $A(x), B(x)$
\[A(x) = \sum_{i=0}^na_ix^i = a_nx^n+a_{n-1}x^{n-1}+\cdots+a_1x+a_0 \\ B(x) = \sum_{i=0}^nb_ix^i = b_nx^n+b_{n-1}x^{n-1}+\cdots+b_1x+b_0\]
将这两个多项式相乘得到 $C(x) = \sum_{i=0}^{2n}c_ix^i$,在这里
\[c_i=\sum_{j+k=i,0\leq j,k\leq n}a_jb_kx^i\]
如果一个个去算 $c_i$ 的话,要花费 $\mathcal O(n^2)$ 的时间才可以完成,但是,这是在系数表示下计算的,如果转换成点值表示,知道了 $A(x), B(x)$ 的点值表示后,由于只有 $n+1$ 个点,就可以直接将其相乘,在 $\mathcal O(n)$ 的时间内得到 $C(x)$ 的点值表示
如果能够找到一种有效的方法帮助我们在多项式的点值表示和系数表示之间转换,我们就可以快速地计算多项式的乘法了,快速傅里叶变换就可以做到这一点
快速傅里叶变换
快速傅里叶变换你可以认为有两个部分,DFT 和 IDFT,分别可以在 $\mathcal O(n\log n)$ 的时间内将多项式的系数表示转化成点值表示,并且转回来,就像下面这张图所示
Cooley-Tukey算法
FFT 最常见的算法是 Cooley-Tukey 算法,它的基本思路在 1965 年由 J. W. Cooley 和 J. W. Tukey 提出的,它是一个基于分治策略的算法
假设现在有一个 $n-1$ 次多项式 $A(x)=\sum_{i=0}^{n-1}a_ix^i$(为了方便,假设 $n=2^m, m\in \mathbb Z$,如果不足可以在高次项系数补成 $0$)
将 $n$ 个 $n$ 次单位根 $\omega_n^0, \omega_n^1, \cdots, \omega_n^{n-1}$ 带入 $A(x)$ 将其转换成点值表达
\[A(\omega_n^k) = \sum_{i=0}^{n-1}a_i\omega^{ki} , k = 0, 1, \cdots, n - 1\]
点值向量 $\vec y=(A(\omega_n^0), A(\omega_n^1), \cdots, A(\omega_n^{n-1}))$ 称作系数向量 $\vec a=(a_0, a_1, \cdots, a_{n-1})$ 的
离散傅里叶变换
(Discrete Fourier Transform, DFT),也记作 $\vec y=DFT_n(\vec a)$
到此为止,直接计算 $DFT_n(\vec a)$ 还是需要 $\mathcal O(n^2)$ 的时间,Cooley-Tukey 算法接下来做的事情是将每一项按照指数奇偶分类
\[\begin{eqnarray*} A(\omega_n^k) &=& \sum_{i=0}^{n-1}a_i\omega_n^{ki} \\ &=& \sum_{i=0}^{\frac{n}{2}-1}a_{2i}\omega_n^{2ki}+\omega_n^k\sum_{i=0}^{\frac{n}{2}-1}a_{2i+1}\omega_n^{2ki} \\ \end{eqnarray*}\]
但是,如果直接这样递归下去,你需要带入的值还是有 $n$ 个,也就是说,现在只是将系数减半,而没有将需要带入的值减半,上面的 $k$ 还是 $0, 1, \cdots, n - 1$,这样的话复杂度还是 $\mathcal O(n^2)$
但是你会注意到,根据准备知识中 $\omega_n^2=\left(e^{\frac{2\pi i}{n}}\right)^2=e^{\frac{2\pi i}{n/2}}=\omega_{\frac{n}{2}}$,并且 $\frac{n}{2}$ 次单位根只有 $\frac{n}{2}$ 个,也就是说,我们要带入的值再平方以后似乎变少了一半?仔细想想就会发现,既然单位根把单位圆等分,那么肯定会对称,也就是有一个正的,就会有一个负的,平方后这两个当然就相同了。严格一点的证明就是
\[\omega_n^{\frac{n}{2}+k} = \omega_n^{\frac{n}{2}}\cdot \omega_n^k = -\omega_n^k\]
这也就是说,对于 $k < \frac{n}{2}$ 的时候
\[A(\omega_n^k) = \sum_{i=0}^{\frac{n}{2}-1}a_{2i}\omega_{\frac{n}{2}}^{ki}+\omega_n^{k}\sum_{i=0}^{\frac{n}{2}-1}a_{2i+1}\omega_{\frac{n}{2}}^{ki}\]
\[\begin{eqnarray*} A(\omega_n^{k+\frac{n}{2}}) &=& \sum_{i=0}^{\frac{n}{2}-1}a_{2i}\omega_{\frac{n}{2}}^{ki}+\omega_n^{k+\frac{n}{2}}\sum_{i=0}^{\frac{n}{2}-1}a_{2i+1}\omega_{\frac{n}{2}}^{ki} \\ &=&\sum_{i=0}^{\frac{n}{2}-1}a_{2i}\omega_{\frac{n}{2}}^{ki}-\omega_n^k\sum_{i=0}^{\frac{n}{2}-1}a_{2i+1}\omega_{\frac{n}{2}}^{ki} \end{eqnarray*}\]
这样我们就将需要带入的值也减少成了 $1, \omega_{\frac{n}{2}}, \omega_{\frac{n}{2}}^2, \cdots, \omega_{\frac{n}{2}}^{\frac{n}{2}-1} $,问题变成了两个规模减半的子问题,只要递归下去计算就可以了,至于复杂度
\(T(n) = 2T(\frac{n}{2})+\mathcal O(n) = \mathcal O(n\log n)\)
傅里叶逆变换(IDFT)
刚刚计算的是 $\vec y = DFT_n(\vec a)$,可以将多项式转化成点值表示,现在为了将点值表示转化成系数表示,需要计算 IDFT(Inverse Discrete Fourier Transform),它是 DFT 的逆
这个问题实际上相当于是一个解线性方程组的问题,也就是给出了 $n$ 个线性方程 \(\begin{equation*} \left\{ \begin{array}{ccccccccc} a_0(\omega_n^0)^{0}&+&\cdots&+&a_{n-2}(\omega_n^0)^{n-2}&+&+a_{n-1}(\omega_n^0)^{n-1}&=&A(\omega_n^0) \\ a_0(\omega_n^1)^{0}&+&\cdots&+&a_{n-2}(\omega_n^1)^{n-2}&+&+a_{n-1}(\omega_n^1)^{n-1}&=&A(\omega_n^1) \\ \vdots & & \vdots & &\vdots& & \vdots & & \vdots\\ a_0(\omega_n^{n-1})^{0}&+&\cdots&+&a_{n-2}(\omega_n^{n-1})^{n-2}&+&+a_{n-1}(\omega_n^{n-1})^{n-1}&=&A(\omega_n^{n-1}) \end{array} \right. \end{equation*}\)
写成矩阵方程的形式就是
\[\begin{equation} \label{IDFT-equation} \begin{bmatrix} (\omega_n^0)^0 & (\omega_n^0)^1 & \cdots & (\omega_n^0)^{n-1} \\ (\omega_n^1)^0 & (\omega_n^1)^1 & \cdots & (\omega_n^1)^{n-1} \\ \vdots & \vdots & \ddots & \vdots \\ (\omega_n^{n-1})^0 & (\omega_n^{n-1})^1 & \cdots & (\omega_n^{n-1})^{n-1} \end{bmatrix} \begin{bmatrix} a_0 \\ a_1 \\ \vdots \\ a_{n-1} \end{bmatrix} = \begin{bmatrix} A(\omega_n^0) \\ A(\omega_n^1) \\ \vdots \\ A(\omega_n^{n-1}) \end{bmatrix} \end{equation}\]
记上面的系数矩阵为 $\mathbf V$ 现在考虑下面这个矩阵 $d_{ij}=\omega_n^{-ij}$ \(\begin{equation*} \mathbf D = \begin{bmatrix} (\omega_n^{-0})^0 & (\omega_n^{-0})^1 & \cdots & (\omega_n^{-0})^{n-1} \\ (\omega_n^{-1})^0 & (\omega_n^{-1})^1 & \cdots & (\omega_n^{-1})^{n-1} \\ \vdots & \vdots & \ddots & \vdots \\ (\omega_n^{-(n-1)})^0 & (\omega_n^{-(n-1)})^1 & \cdots & (\omega_n^{-(n-1)})^{n-1} \end{bmatrix} \end{equation*}\)
设它们相乘后的结果是 $\mathbf E=\mathbf D \cdot \mathbf V$
\[\begin{eqnarray*} e_{ij} &=& \sum_{k=0}^{n-1} d_{ik} v_{kj} \\ &=& \sum_{k=0}^{n-1} \omega_n^{-ik}\omega_n^{kj} \\ &=& \sum_{k=0}^{n-1} \omega_n^{k(j-i)} \end{eqnarray*}\]
当 $i=j$ 时,$e_{ij}=n$
当 $i\neq j$ 时,
\[\begin{eqnarray*} e_{ij} &=& \sum_{k=0}^{n-1} (\omega_n^{j-i})^k \\ &=& \frac{1-(\omega_n^{j-i})^n}{1-\omega_n^{j-i}}\\ &=& 0 \end{eqnarray*}\]
因此可以知道 $\mathbf I_n=\frac{1}{n}\mathbf E$,所以 $\frac{1}{n}\mathbf D = \mathbf V^{-1}$
将 $\frac{1}{n}\mathbf D$ 在 $\ref{IDFT-equation}$ 左乘就会得到
\[\begin{equation*} \begin{bmatrix} a_0 \\ a_1 \\ \vdots \\ a_{n-1} \end{bmatrix} = \frac{1}{n} \begin{bmatrix} (\omega_n^{-0})^0 & (\omega_n^{-0})^1 & \cdots & (\omega_n^{-0})^{n-1} \\ (\omega_n^{-1})^0 & (\omega_n^{-1})^1 & \cdots & (\omega_n^{-1})^{n-1} \\ \vdots & \vdots & \ddots & \vdots \\ (\omega_n^{-(n-1)})^0 & (\omega_n^{-(n-1)})^1 & \cdots & (\omega_n^{-(n-1)})^{n-1} \end{bmatrix} \begin{bmatrix} A(\omega_n^0) \\ A(\omega_n^1) \\ \vdots \\ A(\omega_n^{n-1}) \end{bmatrix} \end{equation*}\]
这样,IDFT 就相当于把 DFT 过程中的 $\omega_n^i$ 换成 $\omega_n^{-i}$,然后做一次 DFT,之后结果除以 $n$ 就可以了。
根据前面的说明,递归实现的 FFT 应该不是什么大问题,下面就直接给出 C++ 代码了(主意 $n$ 要补齐到 $2^m$)