快速傅里叶变换 (fast Fourier transform),即利用计算机计算离散傅里叶变换(DFT)的高效、快速计算方法的统称,简称FFT。快速傅里叶变换是1965年由J.W.库利和T.W.图基提出的。采用这种算法能使计算机计算离散傅里叶变换所需要的乘法次数大为减少,特别是被变换的抽样点数N越多,FFT算法计算量的节省就越显著。
FFT(Fast Fourier Transformation) 是离散傅氏变换(DFT)的快速算法。即为快速傅氏变换。它是根据离散傅氏变换的奇、偶、虚、实等特性,对离散傅立叶变换的算法进行改进获得的。
——百度百科
FFT(Fast Fourier Transformation),中文名快速傅里叶变换,用来加速多项式乘法朴素高精度乘法时间 ),但FFT能
的时间解决。
FFT名字逼格高,也难懂,其他教程写得让人看不太懂,于是自己随便写一下
建议对复数、三角函数相关知识有所耳闻 (不会也无所谓)
下面难懂的点我会从网上盗
系数表示法
一个n-1次n项多项式 可以表示为
也可以用每一项的系数来表示 ,即
这就是系数表示法,也就是平时数学课上用的方法
点值表示法
那么 还可以用
来表示,这就是点值表示法。
对于两个用系数表示的多项式,我们把它们相乘,设两个多项式分别为 ,我们要枚
每一位的系数与
每一位的系数相乘,那么系数表示法做多项式乘法时间复杂度
。
但两个用点值表示的多项式相乘,只需要 的时间。
什么意思呢?
设两个点值多项式分别为:
设它们的乘积是 ,那么
所以这里的时间复杂度只有一个枚举的
复数
毕竟高中有所以不多说
我们把形如
(
均为实数)的数称为复数,其中
称为实部,
称为虚部,
称为虚数单位。当虚部等于零时,这个复数可以视为实数;当
的虚部不等于零时,实部等于零时,常称
为纯虚数。复数域是实数域的代数闭包,也即任何复系数多项式在复数域中总有根。 复数是由意大利米兰学者卡当在十六世纪首次引入,经过达朗贝尔、棣莫弗、欧拉、高斯等人的工作,此概念逐渐为数学家所接受。
——百度百科
初中数学老师会告诉你没有 ,但仅限
扩展至复数集 ,定义
,一个复数
可以表示为
其中 称为实部,
称为虚部,
称为虚数单位
还可以把复数看成复平面直角坐标系上的一个点,比如下面
轴就是实数集中的坐标轴,
轴就是虚数单位
轴
这个点 表示的复数就是
,或者想象它代表的向量为
其实我们还可以自己想象 (其实没有这种表达方式) 它可以表示为
一个复数 的模定义为它到原点的距离,记为
一个复数 的共轭复数为
(虚部取反),记为
复数的运算
复数不像点或向量,它和实数一样可以进行四则运算
设两个复数分别为 ,那么
复数相加也满足平行四边形法则
这张是从网上盗的
即
复数相乘还有一个值得注意的小性质
即模长相乘,极角相加
对于任意系数多项式转点值,当然可以随便取任意 个
值代入计算,但是暴力计算
当然是
的时间。其实可以代入一组神奇的
,代入以后不用做那么多的次方运算,这些
当然不是乱取的,而且取这些
值应该就是 傅里叶 的主意了。
考虑一下,如果我们代入一些 ,使每个
的若干次方等于 1,我们就不用做全部的次方运算了
是可以的,考虑虚数的话
也可以,但只有这四个数远远不够
以原点为圆心,画一个半径为 1 的单位圆,那么单位圆上所有的点都可以经过若干次次方得到 1
傅里叶说还要把它给 等分了,比如此时
橙色点即为 时要取的点,从
点开始,逆时针从 0 号开始标号,标到 7 号,记编号为
的点代表的复数值为
,那么由模长相乘,极角相加可知
,其中
称为
次单位根,而且每一个
都可以求出
(我三角函数不好)
或者说也可以这样解释这条式子
注意
什么的,就容易理解了
那么 即为我们要代入的
推FFT的过程中需要用到 的一些性质
虽然 DFT 搞出来一堆很牛的 作为代入多项式的
值
但只是代入这类特殊 值法的变换叫做 DFT 而已,还是要代入单位根暴力计算
但 DFT 可以分治来做,于是 FFT(快速傅里叶变换) 就出来了
首先设一个多项式
按 下标的奇偶性把
分成两半,右边再提一个
两边好像非常相似,于是再设两个多项式 ,令
比较明显得出
再设 ,把
作为
代入
(接下来几步变换要多想想单位根的性质)
那么对于 的话,代入
和
两个多项式后面一坨东西只有符号不同
就是说,如果已知 和
的值,我们就可以同时知道
和
的值
现在我们就可以递归分治来搞 了
每一次回溯时只扫当前前面一半的序列,即可得出后面一半序列的答案
时只有一个常数项,直接
时间复杂度
想一下,我们不仅要会 ,还要会IFFT(快速傅里叶逆变换)
我们把两个多项式相乘 (也叫求卷积),做完两遍 也知道了积的多项式的点值表示
可我们平时用系数表示的多项式,点值表示没有意义
你有没有想过为什么傅里叶是把 作为
代入而不是别的什么数?
原因是有的但是有我也看不懂
所以只用记住一个结论
意思就是说 和
可以一起搞
有自带的复数模板
库
即表示复数
的实部
#include
#define cp complex
void fft(cp *a,int n,int inv)//inv是取共轭复数的符号
{
if (n==1)return;
int mid=n/2;
static cp b[MAXN];
fo(i,0,mid-1)b[i]=a[i*2],b[i+mid]=a[i*2+1];
fo(i,0,n-1)a[i]=b[i];
fft(a,mid,inv),fft(a+mid,mid,inv);//分治
fo(i,0,mid-1)
{
cp x(cos(2*pi*i/n),inv*sin(2*pi*i/n));//inv取决是否取共轭复数
b[i]=a[i]+x*a[i+mid],b[i+mid]=a[i]-x*a[i+mid];
}
fo(i,0,n-1)a[i]=b[i];
}
两个多项式 相乘再转系数多项式
,通常只用打这么一小段
cp a[MAXN],b[MAXN];
int c[MAXN];
fft(a,n,1),fft(b,n,1);//1系数转点值
fo(i,0,n-1)a[i]*=b[i];
fft(a,n,-1);//-1点值转系数
fo(i,0,n-1)c[i]=(int)(a[i].real()/n+0.5);//注意精度
很明显,FFT只能处理n nn为2 22的整数次幂的多项式
所以在 前一定要把
调到 2 的次幂
这个板子看着好像很优美,但是
递归常数太大,要考虑优化…
这个图也是盗的
这个很容易发现点什么吧?
这样的话我们可以先把原序列变换好,把每个数放在最终的位置上,再一步一步向上合并
一句话就可以 预处理第i ii位最终的位置
fo(i,0,n-1)rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
至于蝴蝶变换它死了其实是我不会
void fft(cp *a,int n,int inv)
{
int bit=0;
while ((1<>1]>>1)|((i&1)<<(bit-1));
if (i
这个板子好像不是那么好背
至少这个板子已经很优美了
本人版权意识薄弱……
本博客部分知识学习于
- https://www.cnblogs.com/RabbitHu/p/FFT.html
- https://www.cnblogs.com/zwfymqz/p/8244902.html?mType=Group#_label3
- https://blog.csdn.net/ggn_2015/article/details/68922404