2019.10.25更新
至此本篇文章距离上次更新已经两年,谢谢各位一直以来的支持。这一次也让我知道了追求一种通透的知识能够得到认可,再次感谢。
0x00 写在前面
为了让更多人能够看到这个教程,希望大家收藏之前,也要点赞哦!!!蟹蟹大家的认可和鼓励。
傅里叶变换
快速傅里叶变换(Fast Fourier Transform,FFT)是一种可在
在算法竞赛中的运用主要是用来加速多项式的乘法。
考虑到两个多项式
我们要求
可以用这段代码表示:
for
思路非常清晰,其时间复杂度是
所以我们来学习快速傅里叶变换。
0x01 关于多项式
多项式有两种表示方法,系数表达法与点值表达法
多项式的系数表示法
设多项式
多项式的点值表示法
将一组互不相同的
其中
定理:
一个次多项式在
个不同点的取值唯一确定了该多项式。
证明:
假设命题不成立,存在两个不同的次多项式
,满足对于任何
,有
。
令,则
也是一个
次多项式。对于任何
,都有
。
即有
个根,这与代数基本定理(一个
次多项式在复数域上有且仅有
个根)相矛盾,故
并不是一个
次多项式,推到矛盾。
原命题成立,证毕。
如果我们按照定义求一个多项式的点值表示,时间复杂度为
已知多项式的点值表示,求其系数表示,可以使用插值。朴素的插值算法时间复杂度为
关于多项式的乘法
已知在一组插值节点
因为
设
如果我们能快速通过点值表式求出系数表示,那么就搭起了它们之间的一座桥了。
这也是快速傅里叶变换的基本思路,由系数表达式到点值表达式到结果的点值表达式再到结果的系数表达式。
0x02 关于复数的基本了解
我们把形如
每一个复数
故每一个复数唯一对应了一个复平面上的向量,每一个复平面上的向量也唯一对应了一个复数。其中
其中复数
由于虚数无法比较大小。复数之间的大小关系只存在等于与不等于两种关系,两个复数相等当且仅当实部虚部对应相等。对于虚部为
复数之间的运算满足结合律,交换律和分配律。
由此定义复数之间的运算法则:
复数运算的加法满足平行四边形法则,乘法满足幅角相加,模长相乘。
对于一个复数
共轭复数有一些性质
0x03 复数中的单位根
复平面中的单位圆

其中
(顺便一提著名的欧拉幅角公式
将单位圆等分成
其中幅角为正且最小的向量称为
(有没有大佬帮我补张图啊,画不来)
其余的
容易看出
对于
所以
关于单位根有两个性质
性质一(又称为折半引理):
证明一:
由几何意义,这两者表示的向量终点是一样的。
证明二:
由计算的公式:
其实由此我们可以引申出
性质二(又称为消去引理)
证明一:
由几何意义,这两者表示的向量终点是相反的,左边较右边在单位圆上多转了半圈。
证明二:
由计算的公式:
最后一步由三角恒等变换得到。
0x04 离散傅里叶变换(Discrete Fourier Transform)
首先我们单独考虑一个
这个过程称为离散傅里叶变换(Discrete Fourier Transform)。
如果朴素带入,时间复杂度也是
所以我们必须要利用到单位根
对于
考虑将其按照奇偶分组
令
则可得到
分类讨论
设
由上文提到的折半引理
对于
其中
由消去引理
故
注意,
于是我们可以知道
如果已知了
而
时间复杂度:
0x05 离散傅里叶反变换(Inverse Discrete Fourier Transform)
使用快速傅里叶变换将点值表示的多项式转化为系数表示,这个过程叫做离散傅里叶反变换(Inverse Discrete Fourier Transform)。
即由
设
我们构造一个多项式
设向量
即
我们考虑对
于是
由和式的性质
令
对其进行化简
设
则
其公比为
当
当
由等比数列求和公式
所以
将
所以
其中
由此得到:
对于多项式
注意到
这个过程称为离散傅里叶反变换。
0x06 关于FFT在C++的实现
首先要解决复数运算的问题,我们可以使用C++STL自带的
也可以自己封装,下面是我封装的复数类。
struct
我们由上面的分析可以得到这个递归的写法。
bool
但是这样的
能不能优化呢?
我们把每一次分组的情况推演出来

观察到每一个位置的数其实都是原来位置上的数的二进制后
于是我们可以想,先将原数组调整成最底层的位置(很好调整吧)。
然后从倒数第二层由底向上计算。
这就是我们一般用来实现
考虑怎么合并?
在
虑合并两个子问题的过程,这一层有
只要将合并顺序换一下,再加入一个临时变量,合并过程就可以在原数组中进行。
令
合并过程如下:
至此,我们可以给出
struct
注意代码中的
因为
所以
其余配图
至此快速傅里叶变换就结束了。
0x07 写在后面
参考资料
复数-Wikipedia
复平面-Wikipedia
Complex Number-Wikipedia