3452 字
17 分钟
快速傅里叶变换

快速傅里叶变换#

点值表示法#

对于一个 n1n-1 阶多项式 P(x)=a0+a1x+a2x2++an1xn1P(x)=a_0+a_1x+a_2x^2+\cdots+a_{n-1}x^{n-1},如果我们已知一个点集 S:{(x0,y0),(x1,y1),,(xn1,yn1)}S:\{(x_0,y_0),(x_1,y_1),\cdots,(x_{n-1},y_{n-1})\},点集 SS 中的所有点都满足 yi=P(xi)y_i=P(x_i),且 xi(i=0,1,,n1)x_i(i=0,1,\cdots,n-1) 各不相同。那么这个点集 SS 就是多项式 P(x)P(x) 的一个点值表示。

插值多项式#

通过点值表达式还原多项式的操作就是插值,这等价于求解一个线性方程组:

{a0+a1x0+a2x02++an1x0n1=y0a0+a1x1+a2x12++an1x1n1=y1a0+a1xn1+a2xn12++an1xn1n1=yn1\begin{cases} a_0+a_1x_{0}+a_2x_{0}^2+\cdots+a_{n-1}x_{0}^{n-1}=y_0\\ a_0+a_1x_{1}+a_2x_{1}^2+\cdots+a_{n-1}x_{1}^{n-1}=y_1\\ \cdots\\ a_0+a_1x_{n-1}+a_2x_{n-1}^2+\cdots+a_{n-1}x_{n-1}^{n-1}=y_{n-1}\\ \end{cases}

表示成矩阵形式就是

[1x0x02x0n11x1x12x1n11xn1xn12xn1n1][a0a1an1]=[y0y1yn1]\begin{bmatrix} 1 & x_0 & x_0^2 & \cdots & x_0^{n-1}\\ 1 & x_1 & x_1^2 & \cdots & x_1^{n-1}\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ 1 & x_{n-1} & x_{n-1}^2 & \cdots & x_{n-1}^{n-1}\\ \end{bmatrix} \begin{bmatrix} a_0\\ a_1\\ \vdots\\ a_{n-1} \end{bmatrix}= \begin{bmatrix} y_0\\ y_1\\ \vdots\\ y_{n-1} \end{bmatrix}

这里系数矩阵 [1x0x02x0n11x1x12x1n11xn1xn12xn1n1]\begin{bmatrix} 1 & x_0 & x_0^2 & \cdots & x_0^{n-1}\\ 1 & x_1 & x_1^2 & \cdots & x_1^{n-1}\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ 1 & x_{n-1} & x_{n-1}^2 & \cdots & x_{n-1}^{n-1}\\ \end{bmatrix}范德蒙德矩阵,该矩阵的行列式值是 0i<j<n(xjxi)\prod_{0\le i\lt j\lt n}(x_j-x_i),因为 xi(i=0,1,,n1)x_i(i=0,1,\cdots,n-1) 各不相同,所以该矩阵的行列式值 0\neq0,即系数矩阵满秩,该线性方程组只能有唯一解。并且可以计算出多项式系数向量:

[a0a1an1]=[1x0x02x0n11x1x12x1n11xn1xn12xn1n1]1[y0y1yn1]\begin{bmatrix} a_0\\ a_1\\ \vdots\\ a_{n-1} \end{bmatrix}= \begin{bmatrix} 1 & x_0 & x_0^2 & \cdots & x_0^{n-1}\\ 1 & x_1 & x_1^2 & \cdots & x_1^{n-1}\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ 1 & x_{n-1} & x_{n-1}^2 & \cdots & x_{n-1}^{n-1}\\ \end{bmatrix}^{-1} \begin{bmatrix} y_0\\ y_1\\ \vdots\\ y_{n-1} \end{bmatrix}

点值表示法下的多项式乘法#

假设多项式 C(x)=A(x)B(x)C(x)=A(x)B(x),其中 A(x)A(x) 的度数(最高次幂)为 nnB(x)B(x) 的度数为 mm,此时 C(x)C(x) 的度数 degC=degA+degB=n+m\deg C = \deg A + \deg B = n+m。如果要通过点值表示法插值出原多项式 C(x)C(x),显然至少需要 n+m+2n+m+2 个点。因此,多项式 A(x),B(x)A(x),B(x) 的点值表示都需要 n+m+2n+m+2 个点,设点集分别为 SA,SBS_A,S_B

{SA:{(x0,y0),(x1,y1),,(xn+m+1,yn+m+1)}SB:{(x0,y0),(x1,y1),,(xn+m+1,yn+m+1)}\begin{cases} S_A:\{(x_0,y_0),(x_1,y_1),\cdots,(x_{n+m+1},y_{n+m+1})\}\\ S_B:\{(x_0,y^\prime_0),(x_1,y^\prime_1),\cdots,(x_{n+m+1},y^\prime_{n+m+1})\} \end{cases}

对于任意点 xkx_kC(xk)=A(xk)B(xk)C(x_k)=A(x_k)B(x_k)。因此,多项式 C(x)C(x) 的点值表示为

{(x0,y0y0),(x1,y1y1),,(xn+m+1,yn+m+1yn+m+1)}\{(x_0,y_0y^\prime_0),(x_1,y_1y^\prime_1),\cdots,(x_{n+m+1},y_{n+m+1}y^\prime_{n+m+1})\}

利用点值表示法求多项式乘法的流程实际上就是:

  1. 将给定的多项式 A(x),B(x)A(x),B(x)(设度数都是 O(N)O(N) 量级)分别转变为点值表示
  2. 通过 A(x),B(x)A(x),B(x) 的点值表示计算出 C(x)C(x) 的点值表示
  3. 通过 C(x)C(x) 的点值表示插值还原出系数多项式

其中第2步的时间复杂度显然为 O(N)O(N),如果我们可以通过合理的点集选取,使得第一步和第三步都能够在 O(NlogN)O(N\log N) 的复杂度下实现,那么我们就得到了一个总复杂度为 O(NlogN)O(N\log N) 的多项式乘法算法。

快速傅里叶变换#

单位复根#

nn 次单位复根 是满足 ωn=1\omega^n=1 的复数 ω\omega,这样的复根恰好有 nn 个:ω=e2πkin(k=0,1,,n1)\omega = e^{\frac{2\pi k i}{n}}(k=0,1,\cdots,n-1)。根据欧拉公式可知,这些复根均匀地分布在复平面的单位圆上。

img

8次单位复根的示例

一些引理#

消去引理#

对任意整数 n0,k0n\ge 0,k\ge 0 以及 d>0d\gt 0

ωdndk=ωnk\omega_{dn}^{dk} = \omega_n^k

从几何意义考虑,这个引理是显然的,并且由此可以得出一个推论:ω2nn=1\omega_{2n}^n = -1

折半引理#

如果 n>0n\gt 0 是偶数,那么 nnnn 次单位复根的平方的集合就是 n2\frac n 2n2\frac n 2 次单位复根的集合。

证明:根据消去引理有 (ωnk)2=ωn/2k(\omega_n^k)^2 = \omega_{n/2}^k。并且注意到,如果对所有 nn 次单位复根进行平方,那么获得每个 n2\frac n 2 次单位根正好 22 次,因为

(ωnk+n/2)2=ωn2k+n=ωn2kωnn=(ωnk)2(\omega_{n}^{k+n/2})^2 = \omega_n^{2k+n} = \omega_n^{2k}\omega_n^n = (\omega_n^k)^2

ωnk\omega_n^kωnk+n/2\omega_{n}^{k+n/2} 的平方相同。

求和引理#

对任意整数 n1n\ge 1 和不能被 nn 整除的非负整数 kk,有

j=0n1(ωnk)j=0\sum_{j=0}^{n-1} (\omega_n^k)^j = 0
j=0n1(ωnk)j=(ωnk)n1ωnk1=(ωnn)k1ωnk1=0\sum_{j=0}^{n-1} (\omega_n^k)^j = \frac{(\omega_n^k)^n-1}{\omega_n^k-1} = \frac{(\omega_n^n)^k-1}{\omega_n^k-1} = 0

离散傅里叶变换(DFT)#

回想一下我们的问题:计算多项式乘法 C(x)=A(x)B(x)C(x)=A(x)B(x)。为了 O(NlogN)O(N\log N) 解决这个问题,我们需要将系数表示转换为点值表示,这里我们对于点值表示法的选取就是 nn 次单位复根,即我们希望计算多项式

A(x)=j=0n1ajxjA(x) = \sum_{j=0}^{n-1} a_jx^j

ωn0,ωn1,,ωnn1\omega_n^0,\omega_n^1,\cdots,\omega_n^{n-1} 处的值(即 nnnn 次单位复根处)。假设 A(x)A(x) 的系数表示用向量 a=(a0,a1,,an1)\vec a = (a_0,a_1,\cdots,a_{n-1}) 表示,定义:

yk=A(ωnk)=j=0n1ajωnkjy_k = A(\omega_n^k) = \sum_{j=0}^{n-1}a_j\omega_n^{kj}

则向量 y=(y0,y1,,yn1)\vec y = (y_0,y_1,\cdots,y_{n-1}) 就是系数向量 a=(a0,a1,,an1)\vec a = (a_0,a_1,\cdots,a_{n-1})离散傅里叶变换(DFT),记作 y=DFTn(a)y=\text{DFT}_n(a)

这里的公式可以与傅里叶变换文中的离散傅里叶变换公式作比较,不难发现本质上是相同的。

快速傅里叶变换(FFT)#

本章节中,假设 nn22 的整数次幂。

**快速傅里叶变换(FFT)**实际上就是利用 nn 次单位复根的性质,在 O(nlogn)O(n\log n) 的时间复杂度下计算出 n1n-1 阶多项式 A(x)=j=0n1ajxjA(x) = \sum_{j=0}^{n-1} a_jx^j 的点值表示。按照下标的奇偶性,分别定义两个多项式 A[0](x),A[1](x)A^{[0]}(x),A^{[1]}(x)

A[0](x)=a0+a2x+a4x2++an2xn/21A[1](x)=a1+a3x+a5x2++an1xn/21\begin{aligned} A^{[0]}(x) &= a_0 + a_2 x + a_4 x^2 + \cdots + a_{n-2}x^{n/2-1}\\ A^{[1]}(x) &= a_1 + a_3 x + a_5 x^2 + \cdots + a_{n-1}x^{n/2-1}\\ \end{aligned}

原多项式 A(x)=A[0](x2)+xA[1](x2)A(x) = A^{[0]}(x^2) + xA^{[1]}(x^2)

比如 77 阶多项式 f(x)=a0+a1x+a2x2+a3x3+a4x4+a5x5+a6x6+a7x7f(x) = a_0+a_1x+a_2x^2+a_3x^3+a_4x^4+a_5x^5+a_6x^6+a_7x^7 就处理为:

f(x)=(a0+a2x2+a4x4+a6x6)+(a1x+a3x3+a5x5+a7x7)f(x) = (a_0+a_2x^2+a_4x^4+a_6x^6)+(a_1x+a_3x^3+a_5x^5+a_7x^7)

其中 f[0](x)=a0+a2x+a4x2+a6x3,f[1](x)=a1+a3x+a5x2+a7x3f^{[0]}(x) = a_0 + a_2x+a_4x^2+a_6x^3,f^{[1]}(x) = a_1+a_3x+a_5x^2+a_7x^3。于是 f(x)=f[0](x2)+xf[1](x2)f(x) = f^{[0]}(x^2)+xf^{[1]}(x^2)

于是,求 A(x)A(x)ωn0,ωn1,,ωnn1\omega_n^0,\omega_n^1,\cdots,\omega_n^{n-1} 处的值就转化为:求两个新的多项式 A[0](x),A[1](x)A^{[0]}(x),A^{[1]}(x)(ωn0)2,(ωn1)2,,(ωnn1)2(\omega_n^0)^2,(\omega_n^1)^2,\cdots,(\omega_n^{n-1})^2 处的值,根据折半引理可知这 nn 个点的点值实际上只有 n2\frac n 2 个不同的取值(因为 (ωnk+n/2)2=(ωnk)2(\omega_{n}^{k+n/2})^2 = (\omega_n^k)^2),这就使得子问题的量级减半了,即求 DFTn\text{DFT}_n 转化为了两个 DFTn2\text{DFT}{\frac n 2} 的子问题,时间复杂度如下:

T(n)=2T(n2)+Θ(n)=Θ(nlogn)T(n) = 2T(\frac n 2) + \Theta(n) = \Theta(n\log n)

快速傅里叶逆变换(Inverse FFT)#

求出多项式的点值表示后,我们还需要将点值表示逆运算为常见的系数表示,这一步就是离散傅里叶逆变换(IDFT)。前文中,我们已经提到了IDFT就是插值,即解方程

[1x0x02x0n11x1x12x1n11xn1xn12xn1n1][a0a1an1]=[y0y1yn1]\begin{bmatrix} 1 & x_0 & x_0^2 & \cdots & x_0^{n-1}\\ 1 & x_1 & x_1^2 & \cdots & x_1^{n-1}\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ 1 & x_{n-1} & x_{n-1}^2 & \cdots & x_{n-1}^{n-1}\\ \end{bmatrix} \begin{bmatrix} a_0\\ a_1\\ \vdots\\ a_{n-1} \end{bmatrix}= \begin{bmatrix} y_0\\ y_1\\ \vdots\\ y_{n-1} \end{bmatrix}

在FFT中,点值表示选用了 nn 次单位复根,上方的方程可以进一步写成

[a0a1an1]=[ωn0ωn0ωn0ωn0ωn0ωn1ωn2ωnn1ωn0ωnn1ωn2(n1)ωn(n1)(n1)]1[y0y1yn1]\begin{bmatrix} a_0\\ a_1\\ \vdots\\ a_{n-1} \end{bmatrix}= \begin{bmatrix} \omega_n^0 & \omega_n^0 & \omega_n^0 & \cdots & \omega_n^0\\ \omega_n^0 & \omega_n^1 & \omega_n^2 & \cdots & \omega_n^{n-1}\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ \omega_n^0 & \omega_n^{n-1} & \omega_n^{2(n-1)} & \cdots & \omega_n^{(n-1)(n-1)}\\ \end{bmatrix}^{-1} \begin{bmatrix} y_0\\ y_1\\ \vdots\\ y_{n-1} \end{bmatrix}

这个单位复根构成的逆矩阵可以表示为以下形式:

[ωn0ωn0ωn0ωn0ωn0ωn1ωn2ωnn1ωn0ωnn1ωn2(n1)ωn(n1)(n1)]1=1n[ωn0ωn0ωn0ωn0ωn0ωn1ωn2ωn(n1)ωn0ωn(n1)ωn2(n1)ωn(n1)(n1)]\begin{bmatrix} \omega_n^0 & \omega_n^0 & \omega_n^0 & \cdots & \omega_n^0\\ \omega_n^0 & \omega_n^1 & \omega_n^2 & \cdots & \omega_n^{n-1}\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ \omega_n^0 & \omega_n^{n-1} & \omega_n^{2(n-1)} & \cdots & \omega_n^{(n-1)(n-1)}\\ \end{bmatrix}^{-1}= \frac 1 n\begin{bmatrix} \omega_n^0 & \omega_n^0 & \omega_n^0 & \cdots & \omega_n^0\\ \omega_n^0 & \omega_n^{-1} & \omega_n^{-2} & \cdots & \omega_n^{-(n-1)}\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ \omega_n^0 & \omega_n^{-(n-1)} & \omega_n^{-2(n-1)} & \cdots & \omega_n^{-(n-1)(n-1)}\\ \end{bmatrix}

我们记 W=[ωn0ωn0ωn0ωn0ωn0ωn1ωn2ωnn1ωn0ωnn1ωn2(n1)ωn(n1)(n1)],W1=1n[ωn0ωn0ωn0ωn0ωn0ωn1ωn2ωn(n1)ωn0ωn(n1)ωn2(n1)ωn(n1)(n1)]W=\begin{bmatrix} \omega_n^0 & \omega_n^0 & \omega_n^0 & \cdots & \omega_n^0\\ \omega_n^0 & \omega_n^1 & \omega_n^2 & \cdots & \omega_n^{n-1}\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ \omega_n^0 & \omega_n^{n-1} & \omega_n^{2(n-1)} & \cdots & \omega_n^{(n-1)(n-1)}\\ \end{bmatrix},W^{-1}=\frac 1 n\begin{bmatrix} \omega_n^0 & \omega_n^0 & \omega_n^0 & \cdots & \omega_n^0\\ \omega_n^0 & \omega_n^{-1} & \omega_n^{-2} & \cdots & \omega_n^{-(n-1)}\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ \omega_n^0 & \omega_n^{-(n-1)} & \omega_n^{-2(n-1)} & \cdots & \omega_n^{-(n-1)(n-1)}\\ \end{bmatrix},只需要验证 WW1=InWW^{-1}=I_n,这里略去计算过程。

因此有

ak=1nj=0n1ωnkjyja_k = \frac 1 n\sum_{j=0}^{n-1}\omega_n^{-kj}y_j

与公式 yk=j=0n1ajωnkjy_k = \sum_{j=0}^{n-1}a_j\omega_n^{kj} 相比较,不难发现这两个问题几乎是一样的(计算 IFFTn(x)\text{IFFT}_n(x) 只需要将向量 y,ay,a 互换,单位复根 ωn\omega_n 取逆)。

算法实现#

分治#

假设我们已知 y[0]=DFT(A[0]),y[1]=DFT(A[1])y^{[0]}=\text{DFT}(A^{[0]}),y^{[1]}=\text{DFT}(A^{[1]})(这里 y[0],y[1]y^{[0]},y^{[1]} 分别是两个长度为 n2\frac n 2 的向量),求解 y=DFT(A)y=\text{DFT}(A)。也就是我们已知 y[0]=(A[0](ωn20),A[0](ωn21),,A[0](ωn2n21))y^{[0]} = (A^{[0]}(\omega_{\frac n 2}^{0}), A^{[0]}(\omega_{\frac n 2}^{1}),\cdots, A^{[0]}(\omega_{\frac n 2}^{\frac n 2 - 1}))y[1]=(A[1](ωn20),A[1](ωn21),,A[1](ωn2n21))y^{[1]} = (A^{[1]}(\omega_{\frac n 2}^{0}), A^{[1]}(\omega_{\frac n 2}^{1}),\cdots, A^{[1]}(\omega_{\frac n 2}^{\frac n 2 - 1})),求 y=(A(ωn0),A(ωn1),,A(ωnn1))y=(A(\omega_n^0),A(\omega_n^1),\cdots,A(\omega_n^{n-1}))

根据前文推导的公式 A(x)=A[0](x2)+xA[1](x2)A(x) = A^{[0]}(x^2) + xA^{[1]}(x^2) 可知 0k<n20\le k\lt \frac n 2 时:

yk=A(ωnk)=A[0](ωn2k)+ωnkA[1](ωn2k)=A[0](ωn2k)+ωnkA[1](ωn2k)(消去引理)=yk[0]+ωnkyk[1]\begin{aligned} y_k &= A(\omega_n^k)\\ &= A^{[0]}(\omega_n^{2k}) + \omega_n^k A^{[1]}(\omega_n^{2k})\\ &= A^{[0]}(\omega_{\frac n 2}^{k}) + \omega_n^k A^{[1]}(\omega_{\frac n 2}^{k})\quad \text{(消去引理)}\\ &= y^{[0]}_k + \omega_n^k y^{[1]}_k \end{aligned}

而后一半的计算则略有不同(0k<n20\le k\lt \frac n 2):

yk+n2=A(ωnk+n2)=A[0](ωn2k+n)+ωnk+n2A[1](ωn2k+n)=A[0](ωn2kωnn)+ωnkωnn2A[1](ωn2kωnn)=A[0](ωn2k)ωnkA[1](ωn2k)=yk[0]ωnkyk[1]\begin{aligned} y_{k+\frac n 2} &= A(\omega_n^{k+\frac n 2})\\ &= A^{[0]}(\omega_n^{2k+n}) + \omega_n^{k+\frac n2} A^{[1]}(\omega_n^{2k+n})\\ &= A^{[0]}(\omega_n^{2k}\omega_n^n) + \omega_n^{k}\omega_n^{\frac n2} A^{[1]}(\omega_n^{2k}\omega_n^n)\\ &= A^{[0]}(\omega_{\frac n2}^{k}) - \omega_n^{k} A^{[1]}(\omega_{\frac n2}^{k})\\ &= y^{[0]}_k - \omega_n^ky^{[1]}_k \end{aligned}

然后就可以写出FFT的代码了:

using cp = complex<double>;
void fft(vector<cp>& a, int inv) {
int n = a.size();
if (n == 1) return;
vector<cp> a0(n / 2), a1(n / 2);
for (int i = 0; i * 2 < n; i++) {
a0[i] = a[i * 2];
a1[i] = a[i * 2 + 1];
}
fft(a0, inv);
fft(a1, inv);
double angle = 2 * pi / n * (inv ? -1 : 1);
cp w(1, 0), wn(cos(angle), sin(angle));
for (int i = 0; i * 2 < n; i++) {
a[i] = a0[i] + w * a1[i];
a[i + n / 2] = a0[i] - w * a1[i];
if (inv) {
a[i] /= 2;
a[i + n / 2] /= 2;
}
w *= wn;
}
}

上方函数中 inv00 时就是一次FFT的正变换;否则是FFT的逆变换。

下面是模板题SPOJ - POLYMUL的一个AC代码:

#include <bits/stdc++.h>
using namespace std;
using db = double;
const db pi = acos(-1);
using cp = complex<db>;
void fft(vector<cp>& a, int inv) {
int n = a.size();
if (n == 1) return;
vector<cp> a0(n / 2), a1(n / 2);
for (int i = 0; i * 2 < n; i++) {
a0[i] = a[i * 2];
a1[i] = a[i * 2 + 1];
}
fft(a0, inv);
fft(a1, inv);
db angle = 2 * pi / n * (inv ? -1 : 1);
cp w(1, 0), wn(cos(angle), sin(angle));
for (int i = 0; i * 2 < n; i++) {
a[i] = a0[i] + w * a1[i];
a[i + n / 2] = a0[i] - w * a1[i];
if (inv) {
a[i] /= 2;
a[i + n / 2] /= 2;
}
w *= wn;
}
}
vector<long long> multiply(vector<int>& a, vector<int>& b) {
int n = 1;
vector<cp> fa(a.begin(), a.end()), fb(b.begin(), b.end());
while (n < a.size() + b.size()) n <<= 1;
fa.resize(n);
fb.resize(n);
fft(fa, 0), fft(fb, 0);
for (int i = 0; i < n; i++)
fa[i] *= fb[i];
fft(fa, 1);
vector<long long> res(n);
for (int i = 0; i < n; i++)
res[i] = round(fa[i].real());
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> a(n + 1), b(n + 1);
for (auto& i : a)
cin >> i;
for (auto& i : b)
cin >> i;
auto c = multiply(a, b);
for (int i = 0; i < n * 2 + 1; i++)
cout << c[i] << " \n"[i == n * 2];
}
return 0;
}

优化(倍增FFT)#

下图展示了一个 77 阶多项式的系数在每一轮递归后的位置:

img

注意每个系数的下标在最终状态(树的叶子结点)时的位置,(0,1,2,3,4,5,6,7)(0,4,2,6,1,5,3,7)(0,1,2,3,4,5,6,7)\rightarrow (0,4,2,6,1,5,3,7)。我们从二进制的角度找找规律:(000,001,010,011,100,101,110,111)(000,100,010,110,001,101,011,111)(000,001,010,011,100,101,110,111)\rightarrow(000,100,010,110,001,101,011,111)。观察到:把原始序列下标的二进制翻转对称一下,就是最终那个位置的下标。这个性质被称为是位逆序置换(bit-reversal permutation)。

位逆序置换显然可以在 O(nlogn)O(n\log n) 的复杂度下实现,但也有更优的 O(n)O(n) 做法,设 R(x)R(x) 表示二进制数 xx 翻转后的数,从小到大递推 R(x)R(x)

  • 首先 R(0)=0R(0)=0
  • 因为我们从小到大递推 R(x)R(x) 的值,因此在求 R(x)R(x) 时,R(x2)R(\lfloor \frac x2\rfloor) 的值已知。只要将 xx 右移一位,然后翻转,再右移一位,就得到了 xx 除了二进制最高位之外所有位的翻转结果,而 R(x)R(x) 的最高位可以通过 xmod2x\bmod 2 求出。于是 R(x)=R(x2)2+(xmod2)×2kR(x) = \bigg\lfloor\frac{R(\lfloor \frac x2\rfloor)}{2}\bigg\rfloor + (x\bmod 2)\times 2^k 这里 2k2^k 表示将 xmod2x\bmod 2 移动至最高位的偏移量。

代码如下:

int bit_reorder(int n, vector<cp>& a) {
int len = __builtin_ctz(n);
if ((int)rev.size() != n) {
rev.assign(n, 0);
for (int i = 0; i < n; i++) {
rev[i] = (rev[i >> 1] >> 1) + ((i & 1) << (len - 1));
}
}
for (int i = 0; i < n; i++) {
if (i < rev[i]) {
swap(a[i], a[rev[i]]);
}
}
return len;
}

也就是说我们能够直接找到FFT递归树最后一层的状态,然后自下而上地合并两个叶子,每一次合并实际上都是在做“已知 y[0]=(A[0](ωn20),A[0](ωn21),,A[0](ωn2n21))y^{[0]} = (A^{[0]}(\omega_{\frac n 2}^{0}), A^{[0]}(\omega_{\frac n 2}^{1}),\cdots, A^{[0]}(\omega_{\frac n 2}^{\frac n 2 - 1}))y[1]=(A[1](ωn20),A[1](ωn21),,A[1](ωn2n21))y^{[1]} = (A^{[1]}(\omega_{\frac n 2}^{0}), A^{[1]}(\omega_{\frac n 2}^{1}),\cdots, A^{[1]}(\omega_{\frac n 2}^{\frac n 2 - 1})),求 y=(A(ωn0),A(ωn1),,A(ωnn1))y=(A(\omega_n^0),A(\omega_n^1),\cdots,A(\omega_n^{n-1}))”这个子问题,从前文已经推出了这个问题的解法:

{yk=yk[0]+ωnkyk[1]yk+n2=yk[0]ωnkyk[1](0k<n2)\begin{cases} y_k = y^{[0]}_k + \omega_n^k y^{[1]}_k\\ y_{k+\frac n 2} = y^{[0]}_k - \omega_n^k y^{[1]}_k\\ \end{cases} \quad (0\le k \lt \frac n2)

注意到,如果我们把向量 y[0]y^{[0]}y[1]y^{[1]} 依次排列,即 (y[0],y[1])=(y0[0],y1[0],,yn21[0],y0[1],y1[1],,yn21[1])(y^{[0]},y^{[1]})=(y_0^{[0]},y_1^{[0]},\cdots,y_{\frac n2-1}^{[0]},y_0^{[1]},y_1^{[1]},\cdots,y_{\frac n2-1}^{[1]}),对比所求向量 y=(y0,y1,,yn2,yn2+1,,yn1)y=(y_0,y_1,\cdots,y_\frac n2,y_{\frac n2+1},\cdots,y_{n-1}),不难发现 yk[0],yk[1]y_{k}^{[0]},y_{k}^{[1]}yk,yk+n2y_k,y_{k+\frac n2} 的位置两两对应,因此我们可以直接在原数组上计算傅里叶变换,无需递归。这个优化被称为是蝴蝶变换(butterfly transform)。

void fft(vector<cp>& a, int inv) {
int n = a.size();
int len = bit_reorder(n, a);
for (int k = 1; k < n; k <<= 1) {
db angle = 2 * pi / (k * 2) * (inv ? -1 : 1);
cp wn(cos(angle), sin(angle));
for (int i = 0; i < n; i += k * 2) {
cp w(1, 0);
for (int j = i; j < i + k; j++) {
cp yk0 = a[j], yk1 = a[j + k] * w;
a[j] = yk0 + yk1;
a[j + k] = yk0 - yk1;
w *= wn;
}
}
}
if (inv) {
for (auto& i : a)
i /= n;
}
}

下面是模板题SPOJ - POLYMUL的倍增法AC代码:

#include <bits/stdc++.h>
using namespace std;
using db = double;
const db pi = acos(-1);
using cp = complex<db>;
vector<int> rev;
int bit_reorder(int n, vector<cp>& a) {
int len = __builtin_ctz(n);
if ((int)rev.size() != n) {
rev.assign(n, 0);
for (int i = 0; i < n; i++) {
rev[i] = (rev[i >> 1] >> 1) + ((i & 1) << (len - 1));
}
}
for (int i = 0; i < n; i++) {
if (i < rev[i]) {
swap(a[i], a[rev[i]]);
}
}
return len;
}
void fft(vector<cp>& a, int inv) {
int n = a.size();
int len = bit_reorder(n, a);
for (int k = 1; k < n; k <<= 1) {
db angle = 2 * pi / (k * 2) * (inv ? -1 : 1);
cp wn(cos(angle), sin(angle));
for (int i = 0; i < n; i += k * 2) {
cp w(1, 0);
for (int j = i; j < i + k; j++) {
cp yk0 = a[j], yk1 = a[j + k] * w;
a[j] = yk0 + yk1;
a[j + k] = yk0 - yk1;
w *= wn;
}
}
}
if (inv) {
for (auto& i : a)
i /= n;
}
}
vector<long long> multiply(vector<int>& a, vector<int>& b) {
int n = 1;
vector<cp> fa(a.begin(), a.end()), fb(b.begin(), b.end());
while (n < a.size() + b.size()) n <<= 1;
fa.resize(n);
fb.resize(n);
fft(fa, 0), fft(fb, 0);
for (int i = 0; i < n; i++)
fa[i] *= fb[i];
fft(fa, 1);
vector<long long> res(n);
for (int i = 0; i < n; i++)
res[i] = round(fa[i].real());
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> a(n + 1), b(n + 1);
for (auto& i : a)
cin >> i;
for (auto& i : b)
cin >> i;
auto c = multiply(a, b);
for (int i = 0; i < n * 2 + 1; i++)
cout << c[i] << " \n"[i == n * 2];
}
return 0;
}
快速傅里叶变换
https://fuwari.vercel.app/posts/fast_fourier_transform/
作者
st1vdy
发布于
2026-03-19
许可协议
CC BY-NC-SA 4.0