快速傅里叶变换FFT(Fast Fourier Transformation)

本文主要讲述如何使用FFT来实现快速多项式乘法。

多项式的表示

系数表示

对于一个多项式

\[A(x)=\sum_{j=0}^{n-1} a_jx^j
\]

向量\(a=(a_0, a_1, ..., a_{n-1})\)为多项式的系数表示。

利用系数表示时,给出\(x_0\),在\(O(n)\)的时间内可以求出\(A(x_0)\)的值,而且\(A(x)\)的加减法也可以在\(O(n)\)内求出,但乘法则需要\(O(n^2)\)的时间。

点值表示

一个多项式\(A(x)\)的点值表示为一个由\(n\)个点值对所组成的集合

\[\begin{Bmatrix}
(x_0, y_0), & (x_1, y_1), & ..., & (x_{n-1}, y_{n-1})
\end{Bmatrix}\]

使得对\(k=0, 1, 2, ..., n-1\), 所有\(x_k\)各不相同,且\(y_k=A(x_k)\).一个多项式可以有很多不同的点值表示。

次数界

如果一个多项式\(A(x)\)的最高次的非零系数为\(a_k\),则称\(A(x)\)的次数为\(k\),记\(degree(A)=k\)。任何严格大于一个多项式次数的整数都是该多项式的次数界。

定理:对于任意\(n\)个点值对组成的集合,其中\(x_k\)都不同,那么存在唯一的次数界为\(n\)的多项式\(A(x)\),满足\(y_k=A(x_k), k=0, 1, 2, ..., n-1\)。

证明:由线性代数中的范德蒙行列式可证。

点值表示的优点

相对于系数表示,点值表示的加减乘法都可以在\(O(n)\)内算出(其中乘法因为当\(x\)一定时,\(y\)值相乘就是之后的\(x\)对应的\(y\)值。

因为一个多项式的点值表示可以有多种,所以可以选取一些较容易算的值,然后快速把一个多项式从系数表示转化为点值表示,运用点值表示相乘,然后再快速从点值表示转化为系数表示。

DFT

因此选择单位复数根作为\(x\),其中\(n\)次单位复数根是满足\(\omega ^n=1\)的复数\(\omega\),\(n\)次单位复数根有\(n\)个:\(\omega_k=e^{2\pi ik/n}, k=0, 1, 2, ..., n-1, \omega_n=e^{2\pi i/n}\)称为主\(n\)次单位根。

引理1(消去引理):对于任何整数\(n, k \geq 0, d>0, \omega_{dn}^{dk}=\omega_{n}^{k}\),特别地,对于任意偶数\(n>0\),有\(\omega_{n}^{n/2}=\omega_2=-1\)

证明:\(\omega_{dn}^{dk}=(e^{2\pi i/dn})^{dk}=(e^{2\pi i/n})^k=\omega_n^k\)

引理2(折半引理):如果\(n>0\)为偶数,那么\(n\)个\(n\)次单位复数根的平方的集合就是\(n/2\)个\(n/2\)次单位复数根的集合。

证明:根据消去引理,对任意非负整数\(k\),有\((\omega_n^k)^2=\omega_{n/2}^k\)。注意,如果对于所有\(n\)词单位复数根进行平方,那么获得每个\(n/2\)次单位根正好\(2\)次,因为\((\omega_n^{k+n/2})^2=\omega_n^{2k+n}=\omega_n^{2k}\omega_n^n=\omega_n^{2k}=(\omega_n^k)^2\)

引理3(求和引理):对任意整数\(n \geq 1\)和不能被\(n\)整除的非负整数\(k\),有\(\sum_{j=0}^{n-1} (\omega_n^k)^j=0\)。

证明:

\[\sum_{j=0}^{n-1} (\omega_n^k)^j=\frac{(\omega_n^k)^n-1}{\omega_n^k-1}=\frac{(\omega_n^n)^k-1}{\omega_n^k-1}=\frac{0}{\omega_n^k-1}=0
\]

\(k\)不能被\(n\)整除保证分母不为\(0\)。

\[y_k=A(\omega_n^k)=\sum_{j=0}^{n-1} a_j\omega_n^{kj}
\]

则\(y=(y_0, y_1, ..., y_{n-1})\)就是系数向量\(a=(a_0, a_1, ..., a_{n-1})\)的离散傅里叶变换(DFT),记为\(y=DFT_n(a)\).

FFT

(以下\(n\)默认为\(2\)的幂)

通过FFT,利用复数单位根的特殊性质,可以在\(O(n log n)\)的时间内算出\(DFT_n(a)\).FFT利用分治策略,采用\(A(x)\)中偶数下标的系数与奇数下标的系数,分别定义两个新的次数界为\(n/2\)的多项式\(A^{[0]}(x), A^{[1]}(x)\):

\[A^{[0]}(x)=a_0+a_2x+a_4x^2+...+a_{n-2}x^{n/2-1}
\]

\[A^{[1]}(x)=a_1+a_3x+a_5x^2+...+a_{n-1}x^{n/2-1}
\]

则\(A(x)=A^{[0]}(x^2)+xA^{[1]}(x^2)\)

因此\(A(x)\)在\(\omega_n^0, \omega_n^1, ..., \omega_n^{n-1}\)的值转换为:

  1. 求次数界为\(n/2\)的多项式\(A^{[0]}(x)\)和\(A^{[1]}(x)\)在点\((\omega_n^0)^2, (\omega_n^1)^2, ..., (\omega_n^{n-1})^2\)的值
  2. 根据\(A(x)=A^{[0]}(x^2)+xA^{[1]}(x^2)\)进行整合

根据折半引理,1中对点求值并不是\(n\)个不同的值,而是\(n/2\)个\(n/2\)次单位复数根。即把一个\(n\)个元素的\(DFT_n(a)\)计算划分为两个规模为\(n/2\)个元素的\(DFT_{n/2}\)计算。

当\(n=1\)时,\(y_0=a_0\omega_1^0=a_0\)

否则设\(y_k^{[0]}=A^{[0]}(\omega_{n/2}^k), y_k^{[1]}=A^{[1]}(\omega_{n/2}^k)\),根据消去引理,有\(y_k^{[0]}=A^{[0]}(\omega_{n}^{2k}), y_k^{[1]}=A^{[1]}(\omega_{n}^{2k})\)

\[y_k=y_k^{[0]}+\omega_n^ky_k^{[1]}
\]

\[=A^{[0]}(\omega_{n}^{2k})+\omega_n^kA^{[1]}(\omega_{n}^{2k})
\]

\[=A(\omega_n^k)
\]

\[y_{k+(n/2)}=y_k^{[0]}-\omega_n^ky_k^{[1]}
\]

\[=y_k^{[0]}+\omega_n^{k+(n/2)}y_k^{[1]}
\]

\[=A^{[0]}(\omega_{n}^{2k})+\omega_n^{k+(n/2)}A^{[1]}(\omega_{n}^{2k})
\]

\[=A^{[0]}(\omega_{n}^{2k+n})+\omega_n^{k+(n/2)}A^{[1]}(\omega_{n}^{2k+n})
\]

\[=A(\omega_n^{k+(n/2)})
\]

逆运算

用矩阵表示\(y=V_na\),则\(V_n\)在\((k, j)\)处元素为\(\omega_n^{kj}\)。求出\(V_n^{-1}\),则可以求出\(a=DFT_n^{-1}(y)\)。

定理:\(V_n^{-1}\)的\((j, k)\)处元素为\(\omega_n^{-kj}/n\)。

证明:考虑\(V_n^{-1}V_n\)中(j, j')处的元素:

\[[V_n^{-1}V_n]_{jj'}=\sum_{k=0}^{n-1}(\omega_n^{-kj}/n)(\omega_n^{kj'})=\sum_{k=0}^{k(j'-j)}/n
\]

若\(j=j'\),则此和为\(1\);否则根据求和引理,此和为\(0\),所以\([V_n^{-1}V_n]\)为单位矩阵。\(a_j=\frac{1}{n}\sum_{k=0}^{n-1}y_k\omega_n^{-kj}\)。

对比DFT:把\(a\)与\(y\)互换,用\(\omega_n^{-1}\)替换\(\omega_n\),并将结果除以\(n\)。因此逆运算也可以在\(O(nlogn)\)内完成。

注意:

  1. 不够\(2\)的幂时要补零
  2. 做完FFT后顺序是乱的,应该按照编号的二进制的翻转对应的十进制进行排序。例如:\(n=8\)时

    0 000 000 0

    1 001 100 4

    2 010 010 2

    3 011 110 6

    4 100 001 1

    5 101 101 5

    6 110 011 3

    7 111 111 7

右边为对应编号

多项式乘法

  1. #include <cstdio>
  2. #include <cmath>
  3. #include <algorithm>
  4. #include <cstdlib>
  5. #include <cstring>
  6. #include <ctime>
  7. #include <deque>
  8. #include <queue>
  9. #include <vector>
  10. #include <map>
  11. #include <complex>
  12. using namespace std;
  13. typedef complex<double> E;
  14. const int maxn=int(1e6)+100;
  15. const double PI=acos(-1);
  16. int n, m;
  17. int rev[maxn];
  18. E a[maxn], b[maxn];
  19. void init()
  20. {
  21. scanf("%d%d", &n, &m);
  22. for (int i=0, tmp; i<=n; ++i)
  23. {
  24. scanf("%d", &tmp);
  25. a[i]=E(tmp, 0);
  26. }
  27. for (int i=0, tmp; i<=m; ++i)
  28. {
  29. scanf("%d", &tmp);
  30. b[i]=E(tmp, 0);
  31. }
  32. m=n+m;
  33. for (n=1; n<=m; n<<=1);
  34. }
  35. void FFT_init()
  36. {
  37. for (int i=0; i<n; ++i)
  38. for (int j=0; 1<<j<n; ++j)
  39. rev[i]=(rev[i]<<1) | (i>>j & 1);
  40. }
  41. void FFT(E *a, int type)
  42. {
  43. for (int i=0; i<=n; ++i)
  44. if (i<rev[i]) swap(a[i], a[rev[i]]);
  45. for (int i=2; i<=n; i<<=1)
  46. for (int j=0; j<n; j+=i)
  47. {
  48. E w(cos(2*PI/i), sin(type*2*PI/i)), wn(1, 0);
  49. for (int k=0; k<i>>1; ++k, wn*=w)
  50. {
  51. E tmp=a[j+k];
  52. a[j+k]=a[j+k]+wn*a[j+k+(i>>1)];
  53. a[j+k+(i>>1)]=tmp-wn*a[j+k+(i>>1)];
  54. }
  55. }
  56. }
  57. void solve()
  58. {
  59. FFT(a, 1);
  60. FFT(b, 1);
  61. for (int i=0; i<n; ++i) a[i]=a[i]*b[i];
  62. FFT(a, -1);
  63. for (int i=0; i<=m; ++i)
  64. printf("%d ", int(a[i].real()/n+0.5));
  65. }
  66. int main()
  67. {
  68. init();
  69. FFT_init();
  70. solve();
  71. return 0;
  72. }

快速傅里叶变换FFT的更多相关文章

  1. [学习笔记] 多项式与快速傅里叶变换(FFT)基础

    引入 可能有不少OIer都知道FFT这个神奇的算法, 通过一系列玄学的变化就可以在 $O(nlog(n))$ 的总时间复杂度内计算出两个向量的卷积, 而代码量却非常小. 博主一年半前曾经因COGS的一 ...

  2. 快速傅里叶变换FFT& 数论变换NTT

    相关知识 时间域上的函数f(t)经过傅里叶变换(Fourier Transform)变成频率域上的F(w),也就是用一些不同频率正弦曲线的加 权叠加得到时间域上的信号. \[ F(\omega)=\m ...

  3. 多项式 之 快速傅里叶变换(FFT)/数论变换(NTT)/常用套路【入门】

    原文链接https://www.cnblogs.com/zhouzhendong/p/Fast-Fourier-Transform.html 多项式 之 快速傅里叶变换(FFT)/数论变换(NTT)/ ...

  4. 快速傅里叶变换(FFT)

    扯 去北京学习的时候才系统的学习了一下卷积,当时整理了这个笔记的大部分.后来就一直放着忘了写完.直到今天都腊月二十八了,才想起来还有个FFT的笔记没整完呢.整理完这个我就假装今年的任务全都over了吧 ...

  5. 快速傅里叶变换(FFT)_转载

    FFTFFT·Fast  Fourier  TransformationFast  Fourier  Transformation快速傅立叶变换 P3803 [模板]多项式乘法(FFT) 参考上文 首 ...

  6. 基于python的快速傅里叶变换FFT(二)

    基于python的快速傅里叶变换FFT(二)本文在上一篇博客的基础上进一步探究正弦函数及其FFT变换. 知识点  FFT变换,其实就是快速离散傅里叶变换,傅立叶变换是数字信号处理领域一种很重要的算法. ...

  7. 浅谈范德蒙德(Vandermonde)方阵的逆矩阵的求法以及快速傅里叶变换(FFT)中IDFT的原理

    浅谈范德蒙德(Vandermonde)方阵的逆矩阵与拉格朗日(Lagrange)插值的关系以及快速傅里叶变换(FFT)中IDFT的原理 标签: 行列式 矩阵 线性代数 FFT 拉格朗日插值 只要稍微看 ...

  8. 快速傅里叶变换FFT / NTT

    目录 FFT 系数表示法 点值表示法 复数 DFT(离散傅里叶变换) 单位根的性质 FFT(快速傅里叶变换) IFFT(快速傅里叶逆变换) NTT 阶 原根 扩展知识 FFT 参考blog: 十分简明 ...

  9. 【学习笔记】快速傅里叶变换(FFT)

    [学习笔记]快速傅里叶变换 学习之前先看懂这个 浅谈范德蒙德(Vandermonde)方阵的逆矩阵的求法以及快速傅里叶变换(FFT)中IDFT的原理--gzy hhh开个玩笑. 讲一下\(FFT\) ...

随机推荐

  1. JavaScript的原型

    //回顾构造函数 function Box(name, age) { this.name = name; //实例属性 this.age = age; this.run = function() { ...

  2. javascript定时器(上)

    (一).setInterval 间隔性 function show(){ alert(‘a’); } setInterval(show,1000); 每隔1000毫秒(1秒)执行一次show这个函数: ...

  3. [转]用Gson来解析Json数据

    转自太阳尚远的博客:http://blog.yeqianfeng.me/2016/03/02/use_gson_to_parse_json/ 在我们实际开发中像Json和XML这两种格式的数据是最常见 ...

  4. Mybatis基础入门 I

    作为ORM的重要框架,MyBatis是iBatis的升级版.Mybatis完全将SQL语句交给编程人员掌控,这点和Hibernate的设计理念不同(至于Hibernate的理念,待我先学习学习). 下 ...

  5. 使用Task简化Silverlight调用Wcf

    原文http://www.cnblogs.com/lemontea/archive/2012/12/09/2810549.html 从.Net4.0开始,.Net提供了一个Task类来封装一个异步操作 ...

  6. RHEL Channel Bonding

    1. 添加 kernel 模块 RHEL5上编辑 /etc/modprobe.conf 加入 alias bond0 bonding options bond0 miimon=100 mode=1   ...

  7. oj 小黑华丽的逆袭机会

    Problem H: 小黑华丽的逆袭机会 Time Limit: 1 Sec  Memory Limit: 128 MB Submit: 79  Solved: 7 [Submit][Status][ ...

  8. 艰苦的RAW格式数据恢复之旅

    艰苦的RAW格式数据恢复之旅 1.RAW 格式形成原因 2.RAW 格式的解决的方法 经验之谈: 1.RAW 格式形成原因 关于形成的原因,在网上搜索了下,千奇百怪的都有,就不一一诉说了,可是有果必有 ...

  9. 3种SQL语句分页写法

    在开发中经常会使用到数据分页查询,一般的分页可以直接用SQL语句分页,当然也可以把分页写在存储过程里,下面是三种比较常用的SQL语句分页方法,下面以每页5条数据,查询第3页为例子: 第一种:使用not ...

  10. 自动备份多个MOSS站点集的脚本

    自动备份多个站点集的脚本(backupscript.bat)可以生成文件名如"Site80_20140327.bak"的备份文件. @echo offecho ++++++++++ ...