前言
啊摸鱼真爽哈哈哈哈哈哈 这个假期努力多更几篇(
理解本算法需对一些《 常 用 》数学概念比较清楚,如复数、虚数、三角函数等(
不会的自己查去(其实就是懒得写了(¬︿̫̿¬☆)
整理了一点点资料(确信 本文仅为作者的总结与完善和本人的理解与观点,有任何误导性错误请多多指出
[WARNING⚠]文笔极差,文章极度啰嗦且可能有些迷惑hhh,尽力了_(:з)∠)_
概述(可略过
离散傅里叶变换(Discrete Fourier Transform,缩写为 DFT),是傅里叶变换在时域和频域上都呈离散的形式,将信号的时域采样变换为其 DTFT 的频域采样。
FFT 是一种高效实现 DFT 的算法,称为 快速傅立叶变换(Fast Fourier Transform,FFT) 。它对傅里叶变换的理论并没有新的发现,但是对于在计算机系统或者说数字系统中应用离散傅立叶变换,可以说是进了一大步。快速数论变换 (NTT) 是快速傅里叶变换(FFT)在数论基础上的实现。[1]
The discovery of the Fast Fourier transformation (FFT) is attributed to Cooley and Tukey, who published an algorithm in 1965. But in fact the FFT has been discovered repeatedly before, but the importance of it was not understood before the inventions of modern computers. Some researchers attribute the discovery of the FFT to Runge and König in 1924. But actually Gauss developed such a method already in 1805, but never published it.
快速傅里叶变换(FFT)的发现归功于Cooley和Tukey,他们在1965年发表了一种算法。但事实上,FFT以前已经被反复发现,但在现代计算机发明之前,人们并不了解它的重要性。一些研究人员将FFT的发现归因于1924年的伦格和科尼格。但实际上,高斯早在1805年就提出了这种方法,但从未发表过。Notice, that the FFT algorithm presented here runs in \(\Theta(n\log_2 n)\) time, but it doesn't work for multiplying arbitrary big polynomials with arbitrary large coefficients or for multiplying arbitrary big integers. It can easily handle polynomials of size \(10^5\) with small coefficients, or multiplying two numbers of size \(10^6\) , but at some point the range and the precision of the used floating point numbers will not no longer be enough to give accurate results. That is usually enough for solving competitive programming problems, but there are also more complex variations that can perform arbitrary large polynomial/integer multiplications. E.g. in 1971 Schönhage and Strasser developed a variation for multiplying arbitrary large numbers that applies the FFT recursively in rings structures running in \(\Theta(n)\) . And recently (in 2019) Harvey and van der Hoeven published an algorithm that runs in true \(\Theta(n\log_2 n)\).
请注意,这里介绍的FFT算法在\(\Theta(n\log_2 n)\)时间内运行,但它不适用于将任意大多项式与任意大系数相乘或将任意大整数相乘。它可以轻松地处理大小为\(10^5\)的多项式和小系数,或者乘以大小为\(10^6\)的两个数字,但在某些情况下,所用浮点数的范围和精度将不再足以给出准确的结果。这通常足以解决竞争性编程问题,但也有更复杂的变体可以执行任意大的多项式/整数乘法。例如,1971年,Schönhage和Strasser开发了一种将任意大数相乘的变体,该变体在\(\Theta(n)\)运行的环结构中递归应用FFT。最近(2019年),Harvey和van der Hoeven发布了一个算法,该算法以true\(\Theta(n\log_2 n)\)运行。[2]
另外,你可能会听说过亿些逼格很高的缩写:
缩写 | 全称 | 作用 | 时间复杂度 |
---|---|---|---|
DFT(Discrete Fourier Transform) | 离散傅立叶变换 | 时频域转换 | \(O(n^2)\) |
FFT(Fast Fourier Transform) | 快速傅立叶变换 | 时频域转换 ( 有精度误差 ) | \(O({\small\texttt{大常数}}+n\log_2n)\) |
NTT/FNTT | 快速数论变换 | 模意义下的时频域转换 | \(O({\small\texttt{小常数}}+n\log_2n)\) |
MTT | 任意模数的NTT | 任意模意义下的时频域转换 | \(O(n\log_2n)\) |
FWT(Fast Wavelet Transform) | 快速沃尔什变换 | 快速集合卷积 | \(O({\small\texttt{不定}})\) |
FMT(Fast Möbius Transform) | 快速莫比乌斯变换 | 逆莫比乌斯反演? | \(O({\small\texttt{不定}})\) |
今天主要来讲前两个
FFT(Fast Fourier Transform),译为快速傅立叶变换,主要用于加速多项式乘法(高精乘)。朴素算法,即DFT(Discrete Fourier Transform),译为离散傅里叶变换,时间复杂度为\(\Theta(n^2)\),而FFT可加将其速到\(\Theta(n\log_2 n)\)
前置知识
有些基础知识需要说一下-O-
多项式系数与点值表示方法
先上一段百度百科对FFT的定义:
快速傅里叶变换 (fast Fourier transform), 即利用计算机计算离散傅里叶变换(DFT)的高效、快速计算方法的统称,简称FFT。快速傅里叶变换是1965年由J.W.库利和T.W.图基提出的。采用这种算法能使计算机计算离散傅里叶变换所需要的乘法次数大为减少,特别是被变换的抽样点数N越多,FFT算法计算量的节省就越显著。
FFT(Fast Fourier Transformation)是离散傅氏变换(DFT)的快速算法。即为快速傅氏变换。它是根据离散傅氏变换的奇、偶、虚、实等特性,对离散傅立叶变换的算法进行改进获得的。
FFT是一种DFT的高效算法,称为快速傅立叶变换(fast Fourier transform)。傅里叶变换是时域一频域变换分析中最基本的方法之一。在数字处理领域应用的离散傅里叶变换(DFT:Discrete Fourier Transform)是许多数字信号处理方法的基础 。
我们可以发现,FFT是在\(\Theta(n\log_2 n)\)的时间内将一个用系数表示的多项式转化成这个多项式的点值表示算法法。
特别地,我们通常将两个多项式相乘称作求卷积(
虽然没啥用(:≡
系数表示
\(设一个一元n-1次n项式f(x)=\sum_{i=0}^{n-1}a_ix^i\)
而系数表示法便是用每一项的系数来表示多项式\(f(x)\),即表示为$$f(x)={a_0,a_1,a_2,…,a_n-1}$$
可发现,运算过程是两个多项式之间每个系数的乘积,复杂度为\(\Theta(n^2)\)
点值表示
\(同样地,设一个一元n-1次n项式f(x)=\sum_{i=0}^{n-1}a_ix^i\)
可将多项式看为在平面直角坐标系上的\(n\)次函数,而将\(n个互不相同的x带入多项式当中,得到n个不同的y值\)
设
那么这\(n\)个点唯一确定该多项式,此时有且仅有唯一一个多项式满足\(\forall k(k\in [0,n)),使得y_k=f(x_k)\),即表示为
Why?
不难想到高斯消元法,是想两点确定直线。多一个点,可确定直线中另一参数,那么也就是说\(n\)个点能确定\(n-1\)个参数(不考虑倍数点之类无意的点)。
假设在计算乘积的两个多项式所选取的\(x\)序列相同,可得
不妨设\(F(x)=f(x)\cdot g(x)\),可得
可发现,上述过程便是把多项式从系数表示转化为点值表示,将点值相乘之后,还原成系数表示,便仍是\(\Theta(n^2)\)的复杂度(疯狂暗示(⊙x⊙;)
复数
很玄学的一个东西
来自百度百科的解释:
我们把形如a+bi(a,b均为实数)的数称为复数,其中a称为实部,b称为虚部,i称为虚数单位。当虚部等于零时,这个复数可以视为实数;当z的虚部不等于零时,实部等于零时,常称z为纯虚数。复数域是实数域的代数闭包,也即任何复系数多项式在复数域中总有根。 复数是由意大利米兰学者卡当在十六世纪首次引入,经过达朗贝尔、棣莫弗、欧拉、高斯等人的工作,此概念逐渐为数学家所接受。
复数的定义
复数集 \(\mathbb{C}\) 中,定义 \(i=\sqrt{−1}\),一个复数 \(z\) 表示为 \(z=a+bi(a,b\in\mathbb{R})\),其中 \(a\) 称为实部,\(b\) 称为虚部,\(i\) 称为虚数单位。特别地,当且仅当 \(a=0,b\not=0\) 时,我们称 \(z\) 为纯虚数,虚数和实数统称为复数。
复数的模(长) 对于虚数 \(z=a+b\text{i}\) ,定义 \(z\) 的模(长)为 \(|z|=\sqrt{a^2+b^2}\)。其几何意义是对应向量的模长,也是对应复平面上的点到原点的距离。
复数的辐角 假设以逆时针为正方向,从\(x\)轴正半轴到已知向量的转角的有向角叫做幅角,
任意一个不为零的复数\(z=a+bi\)的辐角有无限多个值,且这些值相差 \(2\pi\) 的整数倍。特别地,我们把适合于 \(\pi\leq\theta<\pi\) 的辐角\(\theta\)的值,叫做辐角的主值,也称主辐角记作\(\arg(z)\)。辐角的主值是唯一的。
若复数 \(z\) 所表示的向量与 \(x\) 轴正半轴的夹角为 \(\alpha\),则称 \(\theta=\alpha+2k\pi,k\in \mathbb{Z}\) 为复数 \(z\) 的辐角
复数的表示
其实,可以将 \(i\) 理解成虚数单位(就像实数单位1一样),在笛卡尔坐标系中,可将x轴定义为实数轴,y轴定义为虚数轴,这样的坐标系叫做复平面。则可将复数表示在坐标系上的一个点,如复数\(3i+2\)可表示成:
向量 是同时具有大小和方向的量。如上图中的向量\(OB\)记作 \(\overrightarrow{OB}\)
复数的运算
设两个复数分别为\(z_1=a_1+b_1i,z_2=a_2+b_2i\),则:
进行加法运算,实部与虚部分别相加,
特别地,复数的加法满足平行四边形法则,即一组临边之和等于两边相夹的对角线。
表述为\(AB+AD=AC\)
进行乘法运算,拆括号后进行实部与虚部分别的合并(注:\(i^2=-1\)),
根据极坐标系的相关知识,在确定复数 \(z\) 的辐角(主值) \(\theta\) 与模长 \(r=|z|\) 后复数有唯一解[3]
这便是复数的三角表示,显然有
尽管复数的三角形式在加减法时显得十分笨拙,但复数的三角形式在乘除法方面上极具优势,一般地,对于两个复数
根据三角恒等变换的知识,我们有
这说明两个复数相乘得到的复数模长等于两个复数模长之积,得到的复数辐角等于两个复数的辐角之和。特别地,复数 \(z^n\) 的辐角为 \(n\arg(z)\),模长为 \(|z|^n\)。
除法也同理,得到的复数主辐角相减,模长相除。
这便是复数相乘时的性质:模长相乘,极角相加
欧拉公式
便是这个(。・∀・)ノ
\(\sin x,\cos x\)の泰勒展开:
\[\begin{aligned}\sin(x)&=x-\frac{x^3}{3!}+\frac{x^5}{5!}- \frac{x^7}{7!}+\frac{x^9}{9!}\cdots \\ \cos(x)&=1-\frac{x^2}{2!}+\frac{x^4}{4!}-\frac{x^6}{6!}+\frac{x^8}{8!}\cdots\end{aligned} \]
\(e^x\)の展开:
\[e^x=1+x+\frac{x^2}{2!}+\frac{x^3}{3!}+\frac{x^4}{4!}+\frac{x^5}{5!}\cdots \]
因此,将 \(\cos\theta+i\sin\theta\) 用次公式展开,即
可发现与\(e^x\)的展开式十分相似,令 \(x=i\theta\)并带入,即
可发现 \(e^{i\theta}\) 与 \(\cos\theta+i\sin\theta\) 的展开式完全相同,于是欧拉公式得证。
进一步地,欧拉公式可给出一推论:\(e^{\pi i}=-1\)
单位根
代数(学)基本定理
任何复系数一元 \(n\) 次多项式 方程在复数域上至少有一根\((n\geqslant 1)\)。对这个定理的证明目前无法绕开高等数学,由这个定理,我们可以发现任何实系数一元 \(n\) 次多项式方程在复数域上必有 \(n\) 个复数根,只需根据代数基本定理分解因式分解出所有因式即可。
一般地,关于 \(\omega\) 的方程 \(\omega^n=1,n\in \Z\) 在实数域内有且仅有一个根 \(1\),由代数基本定理,在复数域内,这个方程有 \(n\) 个根,它们统称为 \(n\) 次单位根,本文用 \(\omega_n^i\) 来表示最小非负辐角第 \(i+1\) 大的 \(n\) 次单位根,可知 \(\omega_n^0=1\)。
事实上,这个方程在复数域内的 \(n\) 个根为 \(\omega_n^i=\cos(i\cdot\frac {2\pi}{n})+\text{i}\sin(i\cdot\frac{2\pi}{n})\) ,因为其 \(n\) 次乘方对应的辐角为 \(n\cdot i\cdot\frac {2\pi}{n}=2i\pi\),故 \(\arg\left((\omega_n^1)^n\right)=0\), \(|(\omega_n^1)^n|=1\),因而 \((\omega_n^1)^n=1\),满足方程。
把它们画在复平面上可以发现,将单位圆 \(n\) 等分,做 \(n\) 个向量,那么这 \(n\) 个向量对应复数即为 \(n\) 次单位根,以 \(1\) 为起点,逆时针分别为 \(\omega_n^i\)。
例如当 \(n=8\) 时,在复平面上表示为(圆为单位圆)
规定这样的次序还可以带来一些便利,例如 \(\omega_n^i=(\omega_n^1)^i\) ,不妨再规定 \(\omega_n^1=\omega_n\) ,那么便可以使得 \(\omega_n^i\) 既是第 \(i\) 个 \(n\) 次单位根,又表示 \(\omega_n\) 的 \(i\) 次方,同时将 \(\omega_n\) 的上标由 \([0,n-1]\)中的整数扩充到了 \(\mathbb{Z}\)。
单位根的性质
性质1 \(w^0_n=w_n^n=1\)
性质2 \(w_n^0, w_n^1, \cdots,w_n^{n-1}\) 互不相同。
性质3 \(w^{2k}_{2n}=w^k_n\)
证 代入定义式即可,也可直接观察去掉单位圆上的奇数次单位根。
性质4 \(w_n^{k+\frac{n}{2}}=-w_n^k\)
证 代入定义式即可,也可看出旋转半圈前后关于原点对称。
性质5 \(\sum\limits_{i=0}^{n−1}(ω^{j−k}_n)^i=\left\{ \begin{array}{rcl}0, & k≠j \\ n, & k=j\end{array}\right.\)
证 1. 当 \(k\ne j\) 时,根据等比数列的求和公式,可得:
2.当 \(k=j\) 时,可得:
正文
DFT(离散傅里叶变换)
对于任意系数的多项式来说,将其转为点值表示法,暴力取点并计算便是 \(\Theta(n^2)\) 的复杂度,而DFT便是考虑带入单位圆上的单位根,进而求出每个 \(\omega\) 的值
FFT(快速傅里叶变换)
\(\large\texttt{Cooley-Tukey} 算法\)
\(FFT\) 最常见的算法是 \(\texttt{Cooley-Tukey}\) 算法,它的基本思路在 \(1965\) 年由 \(\texttt{J.W.Cooley}\) 和 \(\texttt{J.W.Tukey}\) 提出的,它是一个基于DFT而采取分治策略的算法。
上文中提到的点值表示提到过 \(n\) 次多项式可被 \(n+1\) 个点唯一确定,取点过程中是取复数点的,即 \(n\) 次单位根的 \(0\) 到 \(n-1\) 次幂。
设多项式(函数) \(A(x)=\sum\limits_{i=0}^{n-1}a_ix^i=\) ,按下标 \(i\) 进行奇偶性分类,即
上述等式有着明显的规律所在,很容易便想到设两个多项式:
便有 \(A(x)=A_1(x^2)+xA_2(x^2)\)
ohhhhhhhhh,单位根来惹(。・ω・。)
接着,令 \(x=w_n^k (k<\frac{n}{2})\) 并将其代入函数 \(A(x)\),即
那么,令 \(x=w_n^{k+\frac{n}{2}}\) 代入致函数 \(A(x)\),即
神奇的是,上述的两个函数 \(A(w^k_n)\) 与 \(A(w^{k+\frac{n}{2}}_n)\) 所推出的多项式只相差一个加减符号
那么就是说,这两个函数的值只需在得到 $A_1(w^k_n)和 \(A_2(w^k_{n\over 2})\) 便能求出其值,同时在遍历求得其中一个函数的值时,便可用 \(\Theta(1)\) 的复杂度直接求得另一函数的值,且第一个式子的 \(k\) 在取遍了 \([0,\frac n2-1]\) 的时候,第二个式子取遍了 \([\frac n2,n-1]\) ,即原问题的规模缩小了一半,便可以用递归分治来解决FFT。
此时时间复杂度为 \(\Theta({\small \texttt{大常数}}+nlog_2n)\) 大常数便是复数运算得来的。因为复数乘法速度较慢,所以在数据规模不大时,应尽量避免使用FFT算法。
时间复杂度 \(\Theta(n)\) 。
完了吗?想呢,当然还要逆回去_〆(´Д` )
IFFT(快速傅里叶逆变换)
我们上述的讨论都是基于点值表示法而进行的,所以最后还需再次转化为系数表示法,此过程便是所谓的逆变换。
设 \((y_0,y_1,...,y_{n-1})\) 是 \((a_0,a_1,a_2,\dots,a_{n-1})\) 的傅里叶变换(即点值表示),即 \((y_0,y_1,...,y_{n-1})\) 是 \((a_0,a_1,a_2,\dots,a_{n-1})\) 在 \((w_n^0,w_n^1,\dots,w_n^{n-1})\) 处的值。则有
设 向量\((c_0,c_1,c_2,\dots,c_{n-1})\) 是 \((y_0,y_1,...,y_{n-1})\) 在 \((w_n^0,w_n^{-1},\dots,w_n^{-(n-1)})\) 处的取值。
求C序列同样用 \(\texttt{Cooley-Tukey}\) 算法实现,只需要将只需要单位根变为 \(w_n^{-1}=w_n^{n-1}\) ,相当于顺时针旋转。可以发现,在复平面上, \(w_n^{n-1}\) 和 \(w_n^1\) 的 \(x\) 坐标相同,\(y\) 坐标互为相反数,即为共轭复数。
\((c_0,c_1,c_2,\dots,c_{n-1})\) 满足
根据性质5,便有
- 当 \(j\ne k\) 时,即 \(j-k\ne 0\) ,此时 \(\sum\limits_{i=0}^{n-1}(w_n^{j-k})^i=0\) ,共有 \(n-1\) 种情况
- 当 \(j=k\) 时,即 \(j-k=0\) ,此时 \(\sum\limits_{i=0}^{n-1}(w_n^{j-k})^i=n\) ,共有 \(1\) 种情况。
因此,$$c_k=na_k,\dfrac{c_k}n=a_k$$
如上,我们得到了点值表示法与系数表示法之间的关联式,便可进行逆运算
总结性地来说,可概括为函数分治时所乘单位根的共轭复数,分治后每一项除以\(n\)即为原多项式的每一项系数,因此FFT与IFFT可同时完成。
特别地,C++中带有复数模板库#include
同时,FFT只能处理 \(n\) 为 \(2\) 的整数次幂的多项式(函数)
啊但是,朴素算法的递归常数较大,需考虑优化(σ`д′)σ
FFT优化
非递归
通过观察,我们可得出我们所求的序列便是原序列下表二进制的翻转后得到的结果,因此朴素算法过程中可省略下标的奇偶性分类。此时,秩序 \(\Theta(n)\) 的复杂度来得到我们所求的序列,接着便是向上合并的过程了
蝴蝶操作
啊回头来补嘛没学会(逃(¬︿̫̿¬☆)
题表(坑
总结
后记
引用:
- 摘自OI-Wiki ↩︎
- CP-Algorithms,译文自于百度翻译 ↩︎
- 参考自@linjiayang2016的解法 ↩︎
- 摘自[Cplusplus.com]{http://www.cplusplus.com/reference/complex/} ↩︎
参考文献:
- https://cp-algorithms.com/algebra/fft.html
- https://oi-wiki.org/math/poly/fft/
- https://zhuanlan.zhihu.com/p/143276207
- https://blog.csdn.net/linjiayang2016/article/details/80341958
- https://blog.csdn.net/enjoy_pascal/article/details/81478582
- https://blog.csdn.net/ggn_2015/article/details/68922404
- https://blog.csdn.net/leo_h1104/article/details/51615710
- https://www.cnblogs.com/RabbitHu/p/FFT.html
- https://www.cnblogs.com/zwfymqz/p/8244902.html
- https://baike.baidu.com/item/快速傅里叶变换/214957
- https://baike.baidu.com/item/FFT原理/8966333
- https://baike.baidu.com/item/离散傅里叶变换/6379901
- https://baike.baidu.com/item/复数/254365
嗯,就挺多的(逃
天哪这算是汇总全了吧,码字码去世了一定要留个赞啊啊QAQ缓两天继续更
-END-