FFT(快速傅里叶变换)及优化
最近夏令营投递结束了,抽空来学习一波FFT,其中核心内容包括了DFT(离散傅里叶变换)和IDFT(离散傅里叶逆变换),但他们的复杂度都是$O(n^2)$的,我们通过引入单位根及其相关性质将DFT和IDFT优化为FFT和IFFT,实现$O(nlogn)$的复杂度求解多项式卷积系数及相关问题。
参考blog:
- 洛谷P3803 【模板】多项式乘法(FFT) 的所有题解
前置姿势
欧拉公式
单位根
满足$w^n=1$的复数$w$的集合称为$n$次单位根,通常写作$w_n$
根据欧拉公式,令$x=2\pi$,得到$e^{2\pi i}=1=w_n^n$,得到$w_n=e^{\frac{2\pi i}{n}}$,称为本原单位根,显然
单位根的一些性质(可以用欧拉公式轻松证得)
$wn^k=w{2n}^{2k}$
$w_n^{n+k}=w_n^k$
$w_n^{k+\frac{n}{2}}=-w_n^k$
$w_n^{\frac{n}{2}}=-1$
$w_n^0=w_n^n=1$
多项式的系数表达法
通常情况下,通过多项式方程来表示多项式的方法就是系数表达法,可以直观的看到每项的系数,如$f(x)=3x^3+2x+5$,但如果要通过系数表达法求解两多项式的卷积,显然需要$O(n^2)$的复杂度,不能接受,因此需要考虑换一种方式求解
多项式的点值表达法
众所周知,一个$n$次多项式可以用$n-1$个互不相同的点的坐标来表示,这就是点值表达法,它没有系数表达法那么直观,但显然,如果我们知道两个多项式的系数表达法$(x_0,y0),(x_1,y_1),…$和$(x_0,y_0’),(x_1,y_1’),…$,则可以在$O(n)$的时间内求出$(x_0,y_0y_0’),(x_1,y_1y_1’),…$,即他们卷积的点值表达法
因此,FFT就是通过将多项式的系数表达法转化为点值表达法(DFT),再通过点值表达法求解卷积,再转换回系数表达法(IDFT)的一个计算过程,这个过程乍一看是$o(n^2)$的,因为找出多项式的$n$个点就已经需要$O(n^2)$的时间了(找n个点$O(n)$,计算对应函数值$O(n)$),因此就出现了著名的$FFT$算法
FFT(Fast Fourier Transform)
设$n-1$次多项式$A(x)$的系数为
将下标按照奇偶性分类,得到
设
显然可以得到
此时,将单位根$w_n^k(k<\frac{n}{2})$代入得到
可以惊奇地发现这两个式子就只差了中间一个负号,并且表达式中的$A_1,A_2$规模为$A$的一半,且计算方法相同,因此可以递归分治计算这个式子,递归边界就是仅剩一个项的时候,此时直接返回即可
关于逆变换,先抛出结论:一个多项式在反分治的过程中乘上单位根的共轭复数,分治完的每一项 $/n$即为原多项式的每一项系数
复杂度:$O(nlogn)$,但由于需要递归动态开数组,因此常数较大,但时间复杂度正确
代码(递归版)
1 |
|
优化为迭代版本
到洛谷模板一交,由于它时限定在了2s,因此可以通过(最慢的点要1.8s左右),但是我们能够做得更优,为了解决常数较大的问题,我们需要把递归写法改为迭代写法
我们通过将奇偶分治的最终序列手写出来,可以发现一个规律:原序列和分治到最后的序列的对应二进制表示数互为翻转关系,这样一来,从最终分治的结果,我们先计算$a_0/a_2;a_4/a_6;a_1/a_3;a_5/a_7$,然后计算$a_0/a_1;a_2/a_3;a_4/a_5;a_6/a_7$,得到最终结果
对于二进制翻转的操作,可以使用如下的方法获得,即 得到原序列中的数$i$在翻转序列中的位置$rev[i]$
1 |
|
而求出这个$rev[]$数组还不够,我们还需要将原数组重新排列成为翻转序列的顺序,操作如下
1 |
|
这样一来,我们就可以通过枚举递归分治的区间大小,再枚举区间个数,再枚举区间中每一个数来实现迭代版的FFT
代码(迭代版)
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!