快速傅里叶变换-FFT
本蒟蒻终于来补证明了!!!终于在学完三角函数和复数后理解了一遍
本博客参考自command_block的FFT学习笔记,作本蒟蒻平时复习用
1. DFT & IDFT的思想
众 所 周 知,一个 \(n\) 次的函数 \(f(x)\) 可以由 \(n+1\) 个点表示
因此 \(\{(x_0,y_0),(x_1,y_1),(x_2,y_2),...,(x_{n+1},y_{n+1})\}\) 可以来表示 \(f(x)\)
同时 \(\{(x_0,y_0'),(x_1,y_1'),(x_2,y_2'),...,(x_{n+1},y_{n+1}')\}\) 来表示 \(g(x)\) (注意只有 \(y\) 有 \('\))
如果 \(W(x)=f(x)*g(x)\) ,那么显然 \(W(x_i)=y_i*y_i'\)
那么我们又得到了 \(W(x)\) 的 \(n\) 个点
但不对啊,\(w(x)\) 似乎是 \(2n\) 次的啊
没关系,我们在 \(f(x)\) 和 \(g(x)\) 那取 \(2n+1\) 个点就行了
然后我们再将他转化成系数表示就行了
-
DFT:把系数表达转换为点值表达
-
IDFT:把点值表达转换为系 数表达(DFT的逆运算)
这就是 FFT
的算法流程
但这仍是 \(O(n^2)\) 的
2. DFT 的加速版本
首先,介绍 \(\omega_n^0\) ~ \(\omega_n^{n-1}\) 表示复数平面的单位根
记住!复数的三角表示 \(r(cos\theta+isin\theta)\)
有一个性质为 \(z_1*z_2=r_1r_2(cos(\alpha+\beta)+isin(\alpha+\beta))\)
因此有 \(\omega_n^i*\omega_n^j=\omega_n^{i+j}\)
好,回归正题。我们考虑一个 \(n-1\) 次的函数 \(f(x)\)(也就是有 \(n\) 项)。为了下文方便,我们保证 \(n\) 为 \(2\) 的整次幂
\(f(x)=k_0+k_1x+k_2x^2+...+k_{n-1}x^{n-1}\)
我们考虑将它分成两半:\(Lf(x)=k_0+k_2x^+k_4x^2+...+k_{n-2}x^{n/2-1}\) , \(Rf(x)=k_1+k_3x^+k_5x^2+...+k_{n-1}x^{n/2-1}\)
显然 \(f(x)=Lf(x^2)+x\times Rf(x^2)\)
那我们将单位根 \(\omega_n^k,\ k<n/2\) 代入 \(f(x)\)
-
先代入 \(\omega_n^k\)
\(f(\omega_n^k)=Lf(\omega_n^{2k})+\omega_n^k\times Rf(\omega_n^{2k})\)
因为 \(\omega_n^{2k}=\omega_{n/2}^k\) (\(n\) 为偶数!)
所以 \(f(\omega_n^k)=Lf(\omega_{n/2}^k)+\omega_n^k\times Rf(\omega_{n/2}^k)\)
-
再代入 \(\omega_{n}^{k+n/2}\)
\(f(\omega_{n}^{k+n/2})=Lf(\omega_{n}^{2k+n})+\omega_{n}^{k+n/2}\times Rf(\omega_n^{2k+n})\)
\(\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ =Lf(\omega_{n}^{2k})+\omega_{n}^{k+n/2}\times Rf(\omega_n^{2k})\)
\(\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ =Lf(\omega_{n/2}^{k})+\omega_{n}^{k+n/2}\times Rf(\omega_{n/2}^{k})\)
\(\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ =Lf(\omega_{n/2}^{k})-\omega_{n}^{k}\times Rf(\omega_{n/2}^{k})\)
也就是说,如果我们知道 \(Lf(x),Rf(x)\) 的点值表达式,我们就可以求出 \(f(x)\) 的点值表达式
我们又发现,其实 \(Lf(x),Rf(x)\) 是一个子问题,我们用分治就可以完成了!
3. IDFT 的实现
直接给结论:将 DFT
中 \(\omega_n^1\) 改为 \(\omega_n^{-1}\) ,答案再除以 \(n\) 即可
我们设函数 \(F(x)\) 的点值数列为 \(G\)
即 \(G=DFT(F)\)
易得 \(G\) 的第 \(k\) 个数为 \(G[k]=\sum_{i=0}^{n-1}(\omega_n^k)^iF[i]\)
那么我们要证明的就是 \(n\times F[k]=\sum_{i=0}^{n-1}(\omega_n^{-k})^iG[i]\)
证明:
我们考虑每个 \(j\) 单独贡献,则有分类讨论:
-
当 \(j=k\),则贡献为
\[\sum_{i=0}^{n-1}\omega_{n}^0F[k]=n\times F[k] \] -
当 \(j\not =k\),令 \(p=j-k\),则贡献为
\[\sum_{i=0}^{n-1}\omega_{n}^{ip}F[k+p] \]易证 \(\sum_{i=0}^{n-1}\omega_n^{ip}=0\)
因此当 \(j\not = k\) 时,贡献都为 \(0\)
这本质其实是单位根反演
4. 卡常
到上面,我们就可以码出 FFT
了
但由于常数巨大,我们不得不卡卡常
① 蝴蝶变换
FFT
是满足蝴蝶变换的,就是下面这样:
观察分治结束时各元素的位置变化(注意标号从 \(0\) 开始)
我们可以看到,元素 \(x\) 的二进制与它最后的位置 \(i\) 的二进制恰好相反
比如 \(6=(110)_2\),反转后就是 \(3=(011)_2\)
那我们是不是要用 \(O(nlogn)\) 的时间来预处理?
如果是那我要它干嘛
其实有 \(O(n)\) 的处理方法:
for(int i = 0; i < n; i++)
t[i] = (t[i >> 1] >> 1) | (i & 1 ? n >> 1 : 0);
很好理解的啦
通过蝴蝶变换,我们就可以避免分治过程中的数组拷贝
再配合上迭代实现(就是不要递归),效率将大大提升
② 三次变两次
上述做法中,我们调用了三次的 DFT
那有没有什么方法可以减少次数呢?
还真有,我们考虑复函数 \(W(x)=f(x)+g(x)i\),也就是 \(f(x)\) 是它的实部,\(g(x)\) 是它的虚部
我们考虑将其平方,得到 \(P(x)^2=f(x)^2-g(x)^2+2f(x)g(x)i\)
也就是说,我们在将 \(P(x)\) 转化为点值表达再自己相差后,得到的函数的 虚部除以 \(2\) 就是我们要求的答案
最后做 IDFT
即可
(不得不说,真的快了不少)