两个 n 项多项式按定义相乘需要 O(n2) 次系数乘加。FFT 的核心不是“神奇地加速卷积”,而是把多项式换到一个乘法特别容易的表示里。
1. 系数表示与卷积
设
A(x)=i=0∑n−1aixi,B(x)=j=0∑n−1bjxj.
乘积 C(x)=A(x)B(x) 的系数为
ck=i+j=k∑aibj.
这就是离散卷积。直接计算每个 (i,j) 组合需要 Θ(n2)。
2. 点值表示
次数小于 d 的多项式由任意 d 个互异点上的函数值唯一确定。因此可以:
- 在足够多的点上求 A 和 B 的值;
- 逐点相乘 C(xk)=A(xk)B(xk);
- 由点值插值恢复 C 的系数。
逐点相乘只需 O(n)。真正的难点是如何快速完成“系数 ↔ 点值”的转换。
3. 为什么选单位根
取 n 次复单位根
ωn=e2πi/n.
它们具有:
ωnn=1,ωnk+n=ωnk,ωn2k=ωn/2k.
最后一个性质让偶数次幂和奇数次幂都能变成规模减半的问题。
4. DFT
离散傅里叶变换把系数向量 a0,…,an−1 变为单位根上的点值:
yk=A(ωnk)=j=0∑n−1ajωnjk,k=0,…,n−1.
直接计算仍是 O(n2)。FFT 是计算 DFT 的分治算法。
5. 偶奇拆分
把多项式写成
A(x)=Aeven(x2)+xAodd(x2),
其中
Aeven(x)=a0+a2x+a4x2+⋯,
Aodd(x)=a1+a3x+a5x2+⋯.
在 x=ωnk 处:
A(ωnk)=Aeven(ωn/2k)+ωnkAodd(ωn/2k).
而对另一半点 k+n/2:
A(ωnk+n/2)=Aeven(ωn/2k)−ωnkAodd(ωn/2k).
一次得到一对输出,递推式为
T(n)=2T(n/2)+O(n)=O(nlogn).
6. 逆变换
逆 DFT 与 DFT 形式几乎相同,只需把单位根换成共轭并除以 n:
aj=n1k=0∑n−1ykωn−jk.
因此插值也能用一次 FFT 在 O(nlogn) 内完成。
7. 多项式乘法流程
若两个多项式次数都小于 n,乘积次数小于 2n−1:
- 把系数补零到不小于 2n 的 2 的幂 N;
- 计算 FFT(A) 和 FFT(B);
- 对每个频点做 Yk=YkAYkB;
- 对 Y 做逆 FFT;
- 按需要对浮点误差四舍五入。
总复杂度为
O(NlogN).
8. 一个四项例子
对
A(x)=a0+a1x+a2x2+a3x3,
拆成
Aeven(x)=a0+a2x,Aodd(x)=a1+a3x.
先在 ω20=1 和 ω21=−1 上分别计算两个一次多项式,再通过加减组合得到 A 在 1,i,−1,−i 四个点上的值。
9. 数值误差与 NTT
复数 FFT 使用浮点运算,长卷积或大整数乘法会有舍入误差。若系数在模素数意义下运算,可使用数论变换 NTT:把复单位根换成有限域中的原根,获得精确整数结果。
FFT/NTT 的应用包括多项式乘法、大整数乘法、信号卷积、相关计算和快速匹配。
10. 易错点
- 补零长度不足会发生循环卷积混叠;
- 逆变换忘记除以 n;
- 单位根正负号约定前后不一致;
- 误以为逐点乘法的点数等于原多项式次数,而不是乘积所需的系数数;
- 浮点结果直接转整数而不四舍五入。