快速 Fourier 变换是离散 Fourier 的一种优化,在 OI 中常用来加速一些卷积。
算法思路
首先,什么是 Fourier 变换?
连续的 Fourier 变换将可积函数 $f:\R\to\C$ 表示为复指数函数的积分或级数形式:
,其中 $\xi$ 为任意实数;Fourier 逆变换用 $\hat f$ 确定 $f$:
,其中 $x$ 为任意实数。
离散 Fourier 变换就是将连续的积分转为离散的求和。
如果给定多项式 $f(x)$、$g(x)$,求多项式 $h(x)=f(x)\cdot g(x)$,容易给出一种朴素的 $\Theta(\deg f\cdot \deg g)$ 的算法。
考虑用将特定选取的 $x_1,\cdots, x_n$ 代入多项式 $f$ 得到的结果 $f(x_1),\cdots,f(x_n)$ 来表示 $f$,用同样的方法表示 $g$,再将结果直接点乘,再过一次逆变换,就可以得到 $h$ 了。
根据连续 Fourier 变换的形式不难知道,这里的 $x_1,\cdots,x_n$ 为单位根 $\omega_n^0,\cdots, \omega_n^{(n-1)}$。
称代入 $\omega_n^i$ 的过程为 离散 Fourier 变换(Discrete Fourier Transform, DFT)。
朴素的做法仍然是 $\Theta(n^2)$ 的,我们考虑分治:
发现括号内形式一样,令 $f_1(x)=a_0+a_2x+\cdots$,$f_2(x)=a_1+a_3x+\cdots$,则
当然,为了使分治后两个多项式度数相同,我们需要令 $n=2^k$。
将 $\omegan^k$ 代入:$f(\omega_n^k)=f_1(\omega{n/2}^k)+\omega{n/2}^kf_2(\omega{n/2}^k)$。我们仅需要知道 $f1,f_2$ 对 $\omega{n/2}^i$ 的所有取值即可算得 $f$ 对 $\omega_n^i$ 的所有取值。
于是我们只需要先将奇偶次项分离,分别计算后再进行合并,时间复杂度 $T(n)=2T(\dfrac n2)+\Theta(n)=\Theta(n\log n)$。
发现奇偶次项分离的空间复杂度为 $\Theta(n\log n)$,且内存开销较大,难以接受。
观察系数分离情况:
不难证明 $i$ 在结束后的位置 $j$ 一定为 $i$ 在 $k$ 位二进制表达下逆序的值 $\operatorname{rev}(i)$。
利用 $\operatorname{rev}(i)=\left\lfloor\dfrac{\operatorname{rev}\left(\left\lfloor\dfrac i2\right\rfloor\right)}2\right\rfloor+[i\bmod 2=1]\cdot\dfrac n2$ 能够在 $\Theta(n)$ 的时间复杂度递推求得 $\operatorname{rev}(i)$。
离散 Fourier 变换的逆变换称为 逆离散 Fourier 变换(Inverse Discrete Fourier Transform, IDFT)。
容易知道,若视 $[a_1,\cdots, a_n]^{\rm tr}$ 与 $[y_1,\cdots, y_n]^{\rm tr}$ 为列向量,则 DFT 本质上为左乘一个矩阵
容易验证该矩阵的逆矩阵为其每一项取倒数再除以 $n$。
所以 IDFT 时仅需将 DFT 用的单位根取倒数,算完除以 $n$ 即可。
代码
多项式乘法:
1 | /** |
洛谷的坑人模板:
1 | /** |
高精度乘法模板
1 | /** |
三次变两次优化
正常的 FFT 要做三次 DFT,非常不优秀。考虑如下变换:
,其中平方是指点乘自身,于是我们能用两次 DFT 解决:
1 | /** |