先来回忆一下高精度乘法的原理
对于正整数相乘,将A视为, B同理为, 将A * B视作多项式相乘,得到C的多项式形式为,然后逐一进位, 即可得到C的值。
其中,
这种方法的时间复杂度为级别, 而FFT可以以的复杂度计算得到多项式乘积。
多项式的点值表示
设n次多项式,
由线性代数知识可知,若知道了n+1组不同的[x, f(x)]的关系,即可得到一个n+1 x n+1的多元线性方程组,且这个方程组有唯一解。
也就是说, 对于一个n次多项式, 我们可以用来定义它, 其中。
设n次多项式和m次多项式, 由于的结果最多是n + m次,所以我们将两个多项式进行零填充到n + m次, 此时如果 则 可以看到, 在点值形式下, 计算仅需,如果我们能快速地将多项式转换为点值形式, 计算后,再将结果快速地转换为系数形式,就能以较低的时间复杂度完成多项式的计算。
离散傅里叶变换(DFT)
我们要将n次多项式转换为点值形式, 该怎么做呢?
可以任意取n + 1个x值, 带入进行计算,但这样时间复杂度是!
数学家傅里叶取了一组特殊的点带入, 使得其可以进行优化。
单位根
若复数满足, 则称为n次单位根。
将单位圆n等分,即可得到圆上的n个坐标点,而这n个坐标点表示的复数都是n次单位根。其中第k个n次单位根称为, 特变的, , 即(1,0)为起点, 逆时针. 易得
单位根的性质
在本文中需要用到单位根的如下性质(均可通过展开式推导得出):
而离散傅里叶变换就是一组单位根作为x值, 但如果还是直接代入计算的话,复杂度仍然没有变化。
快速傅里叶变换(FFT)
快速傅里叶变换是一种能在计算机中快速地计算离散傅里叶变换的算法。
FFT的基本思想是分治。
设n-1阶多项式,且为2的整数幂(不够则零填充),上面我们知道要求的点值形式, 也就是对每一个k求出。
将按奇数次幂和偶数次幂分为两部分: 设 则 代入得 且 故求出和只需要先求出和.
假设我们已经求出了g和h在的值,就可以求出所有的f的点值表示。
g和h的问题规模都是f的一半,所以时间复杂度为
离散傅里叶逆变换(IDFT)
用矩阵乘法表示DFT的过程为 FFT结束后我们得到了最右边的结果,所以要得到系数矩阵只需要让结果乘以中间大矩阵的逆矩阵即可。
怎么求这个矩阵的逆矩阵呢?这里的结论是每一项取倒数,再除以多项式的长度n,至于证明可以参见快速傅里叶变换(FFT)超详解- 知乎 (zhihu.com)
快速傅里叶逆变换(IFFT)
结果矩阵乘以逆矩阵这个过程显然和FFT的过程很相似,考虑怎么转化。
观察,要求的IFFT, 首先求出和的IFFT, 且将x取反,最后加和后再除以n, 即可完成。
所以IFFT和FFT可以公用一套代码,只需要用一个标志位来控制关键位置的取反。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| // // Created by trudbot on 2023/7/14. // #include "complex"
//sign为1时是FFT, -1是IFFT //n必须为2的整数次幂 const double pi = acos(-1); void FFT(std::complex<double> *a, int n, int sign) { if (n == 1) return; std::complex<double> g[n / 2], h[n / 2]; for (int i = 0, j = 0; i < n; i += 2, j ++) { g[j] = a[i], h[j] = a[i + 1]; } FFT(g, n / 2, sign), FFT(h, n / 2, sign); std::complex<double> w(1, 0), u(cos(2 * pi) / n, sign * sin(2 * pi / n)); for (int i = 0; i < n / 2; i ++, w *= u) { a[i] = g[i] + w * h[i]; a[i + n / 2] = g[i] - w * h[i]; } }
void DFT(std::complex<double> *a, int n) { FFT(a, n, 1); }
void IDFT(std::complex<double> *a, int n) { FFT(a, n, -1); for (int i = 0; i < n; i ++) { a[i] /= n; } }
|
参考
超详细易懂FFT(快速傅里叶变换)及代码实现_Trilarflagz的博客-CSDN博客
快速傅里叶变换(FFT)超详解- 知乎 (zhihu.com)
傅里叶变换与大数乘法- Free_Open - 博客园 (cnblogs.com)
如何通俗易懂地解释卷积?- 知乎 (zhihu.com)
如何通俗地解释什么是离散傅里叶变换?- 知乎 (zhihu.com)
快速傅里叶变换- OI Wiki (oi-wiki.org)
欧拉公式_百度百科(baidu.com)
如何用latex编写矩阵(包括各类复杂、大型矩阵)?- 知乎 (zhihu.com)