引入
首先,FFT是快速傅里叶变换的简称。傅里叶变换是一个数学中使用的对函数的变换,而离散傅里叶变换顾名思义就是对于离散点的傅里叶变换。
那么,在OI中,傅里叶变换的最大意义,就在于加速向量卷积,即多项式乘法。
多项式乘法
系数表示法
现有两个多项式
A(x)=∑i=0nai∗xiB(x)=∑i=0nbi∗xi
我们这样定义它们的乘法
C(x)=∑i=02n−1(∑j=0iaj∗bi−j)xi=A(x)∗B(x)
计算它们的乘法,朴素的做法需要
O(n2) 的复杂度
我们将上述
A(x) 中的系数写作一个向量
A ,这种方法称为系数表示法
点值表示法
现在来考虑另一种方法:点值表示法
设向量 A=(A(x1),A(x2)...A(xn)) 则 A 称作 A(x) 的点值表示法
点值表示法有如下性质
性质一
唯一性:一个n维点值与一个n维系数一一对应
性质二
C(x)=∑i=02n−1(∑j=0iaj∗bi−j)xi
=∑i=02n−1∑j=0i(ajxj)∗(bi−jxi−j)
=∑j=02n−1(ajxj)∗∑i=02n−1(bixi)
点值下的乘法复杂度是
O(n) 的
快速傅里叶变换
单位根
满足
xn=1
的复数,称为单位根,记作
ωn
它可以看作是复平面上x轴绕逆时针方向旋转
2πn 的复数
单位根有一些显而易见的性质
ω2m2n=ωmn
ωm2n=−ωn+m2n
蝴蝶变换
我们利用单位根作特殊值进行系数表示与点值表示的变换
设 A0(x)是A(x)的偶次项的和,A1(x)是A(x)的奇次项的和
那么有
A(ωmn)=A0((ωmn)2)+ωmnA1((ωmn)2)
=A0(ωmn/2)+ωmnA1(ωmn/2)
A(ωm+n/2n)=A0((ωm+n/2n)2)+ωm+n/2nA1((ωm+n/2n)2)
=A0(ωmn/2)+ωm+n/2nA1(ωmn/2)
=A0(ωmn/2)−ωmnA1(ωmn/2)
显然 A0,A1 都是子问题,我们知道了它们的点值表示,就能线性地求出 A(x) 的点值表示
每一层对于一个确定的 m ,我们用对应的两个值来更新出当前的值,称这个操作为“蝴蝶变换”
这样我们可以用 O(nlogn) 的时间完成系数到点值的转换
逆离散傅里叶变换
从定义出发,从系数表示到点值表示的变换可以看成下面一个矩阵乘法
⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪111...11w1nw2n...wn−1n1w2nw4n...w2(n−1)n...............1wnnw2nn...wn(n−1)n⎫⎭⎬⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪a0a1a2...an−1⎫⎭⎬⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪=⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪y0y1y2...yn−1⎫⎭⎬⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪
假如我们想通过 y 得到 a 关键在于得到左边的矩阵的逆矩阵
这个矩阵是这样的
⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪111...11w−1nw−2n...w−(n−1)n1w−2nw−4n...w−2(n−1)n...............1w−nnw−2nn...w−n(n−1)n⎫⎭⎬⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪
这样,我们只需要把代进去的向量翻转一下,然后进行FFT即可
迭代/非迭代
根据上面的理论,我们已经可以进行迭代版的FFT了
但是经过观察,我们发现:
我们每次把当前序列的偶数项,奇数项分别提取出来分别列在左右,这样序列里的每个数最后都会在一个位置上,我们可以通过这个循环把每一项最终的位置算出来
for(int i=0;i<n;i++)R[i]=(R[i>>1]>>1)|((i&1)<<(L-1));
然后我们就可以实现非迭代的FFT了,相比之下会快很多