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

更改了一些以前不大正确的地方,又添加了一些推导,证明实在不会。

有一些公式,但个人觉得还是比较好理解。可能还会有错误,希望大佬友情指出。

最后,祝各位看官新年快乐.

回家过寒假去咯(虽然就\(4\)天\(qwq\))

多项式

一个次数界为\(n\)的多项式\(A(x) = \sum_{i = 0}^{n - 1}a_ix^i\)

次数界为\(n\)说明这个多项式最高次项的指数小于\(n\)

系数表示法

我们可以只保留多项式每项的系数来描述这个多项式。即\(a_i\)表示指数为\(i\)的项的系数

运算

多项式之间的加运算,只要将对应项的系数相加即可。

即若\(C(x) = A(x) + B(x)\)那么有\(C(x) = \sum_{i = 0}^{n - 1}{(a_i+b_i)x^i}\)

多项式之间的乘法。

若\(C(x) = A(x) + B(x)\)那么\(C(x) = \sum_{i = 0}^{n - 1}{\sum_{j = 0}^ia_jb_{i - j}x^i}\)

其中\(C(x) = \sum_{i = 0}^{n - 1}{\sum_{j = 0}^ia_jb_{i - j}x^i}\)叫做卷积

可以看出暴力计算卷积的复杂度为\(O(n^2)\)

点值表示法

我们可以选\(n\)个值代入多项式从而得出\(n\)个二元组\((x_i,y_i)\)。可以证明,通过这\(n\)个点对可以确定这个多项式。

还原这个多项式用高斯消元即可。

点值表示法的优点是进行多项式的加法和乘法只要将\(y_i\)相加或者相乘即可。

求值与插值

考虑如何可以比较快速的计算卷积。因为点值表示计算乘法比较方便。所以我们可以把系数表示法转换为点值表示法。然后进行乘法。然后在转换回来。就可以计算卷积了。

把系数表示法转化为点值表示法称为求值,把点值表示法转换为系数表示法称为插值。

拉格朗日插值公式

\(A(x) = \sum_{i = 0}^{n-1}y_i\frac{\prod_{j\ne i}{(x - x_j)}}{\prod_{j \ne i}{(x_i - x_j)}}\)

利用拉格朗日插值公式可以在给定\(n\)个点值表示法的情况下\(O(n^2)\)时间内计算出原二项式在某个位置的值

离散傅里叶变换(DFT)

离散傅里叶变换实现了两种表示法之间的转换,复杂度是\(O(n^2)\)

复数

复数是形如\(a+bi\)的数,其中\(a,b\)为实数。\(a\)称为实部,\(b\)称为虚部。\(i\)为虚数单位,定义\(i^2 = -1\)

复数是数的范围在实数上的扩展

将复数\(a+bi\)表示在数轴上,是一个\((a,b)\)的向量.

复数的运算

复数相加将\(a\)和\(b\)分别相加即可。

复数相乘,其实将i看作未知量相乘就行了。即\((a+bi)*(x+yi) = (ax - by) + (ay + bx)i\)

单位圆

在数轴上以原点为圆心半径为\(1\)的圆。

单位根

\(n\)次单位根满足\(\omega^n = 1\)。\(\omega 就是n次单位根\)

表现在单位圆上就是单位圆的\(n\)等分点

我们用\(\omega_n\)表示主\(n\)次单位根也就是从\((1,0)\)开始数第一个n次单位根

那么可以得到其他的\(n\)次单位根分别为\(\omega_n^0,\omega_n^2,\omega_n^3,.....\omega_n^{n - 1}\)

性质:

\(\omega_n^n = 1\)当n为偶数时,\(\omega_n^{n/2} = -1\)

\(\sum_{i=0}^{n-1}\omega_n^i=0\)

欧拉公式

\(e^{ix} = cos(x)+i * sin(x)\)

\(\omega_n=e^{2\pi i/n} = cos(\frac{2\pi}{n})+i*sin(\frac{2\pi}{n})\)

用来求主\(n\)次单位根

转换

有了上面这些铺垫,就可以进行转换了。

将\(n\)个\(n\)次单位根带入原来的多项式,就可以得到\(n\)个点值表示法。

那么怎么将点值表示法转换为系数表示法呢?

这就是用\(n\)次单位根的原因

我们将得到\(n\)个\(y\)作为另一个多项式\(B(x)\)的系数

然后取单位根倒数带入就能得到原来的多项式\(A(x)\)啦

这样就成功的实现了点值表示法与系数表示法之间的转换

快速傅里叶变换(FFT)

用\(DFT\)计算卷积的复杂度是\(O(n^2)\)的。利用快速傅里叶变换可以优化到\(O(nlogn)\)

分治

考虑一个次数界为\(n\)的多项式\(A(x)\)求他的\(DFT\)

这里默认\(n\)为偶数,如果\(n\)不是偶数,那么只要加上一个系数为\(0\)的项即可将他填充为偶数

然后我们根据下标的奇偶性将他分为两部分。

\[A_{(even)}(x) = a_0 + a_2x + a_4x^2....+ a_{n-2}x^{\frac{n}{2} - 1}
\]

\[A_{(odd)}(x) = a_1 + a_3x+a_4x^2....+a_{n-1}x^{\frac{n}{2}-1}
\]

然后就能得到\(A(x)=A_{(even)}(x^2) + xA_{(odd)}(x^2)\)

然后发现\(A_{(even)}(x)\)和\(A_{(odd)}(x)\)是可以用同样的方法递归计算的。所以就可以进行分治然后递归运算。

那么我们需要带入这\(n\)个单位根,应该怎么计算呢。

下面进行推导

假如现在要把\(\omega_{n}^i\)代入

依据上面的式子

\[A(\omega_n^i)=A(\omega_n^{2i}) + \omega_n^{i}A(\omega_n^{2i})
\]

\[=A(\omega_{\frac{n}{2}}^i)+\omega_n^iA(\omega_{\frac{n}{2}}^i)
\]

然后考虑\(A(\omega_n^{i + \frac{n}{2}})\)

\[A(\omega_n^{i + \frac{n}{2}}) = A(\omega_{n}^{2i+n}) + \omega_n^{i + \frac{n}{2}} A(\omega_{n}^{2i+n})
\]

\[=A(\omega_{n}^{2i}\omega_n^n) + \omega_n^{i} \omega_{n}^{ \frac{n}{2}} A(\omega_{n}^{2i}\omega_n^n)
\]

\[=A(\omega_{\frac{n}{2}}^i) - \omega_n^{i}A(\omega_{\frac{n}{2}}^i)
\]

然后就可以递归的愉快的计算出将这\(n\)个单位根代入所得到的值

优化

递归实现\(FFT\)非常慢。所以要用非递归的方法来实现

我们将递归实现的过程写下来

0 1 2 3 4 5

0 2 4|1 3 5

0 4|2|1 5|3

0|4|2|1|5|3

然后看出每个数字递归到最后的为是为当前位置二进制表示法翻转之后的数字。

然后就可以先把每个数字都放到最后的位置上面,然后再合并就行了。

代码

递归

/*
* @Author: wxyww
* @Date: 2019-02-01 21:09:51
* @Last Modified time: 2019-02-01 21:47:34
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<ctime>
#include<cmath>
#include<bitset>
using namespace std;
typedef long long ll;
const int N = 3000100;
const double pi = acos(-1.0);
ll read() {
ll x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
return x*f;
}
struct complex {
double x,y;
complex() {x = y = 0;}
complex(double xx,double yy) {
x = xx;y = yy;
}
}A[N],B[N];
complex operator * (complex a,complex b) {
return complex(a.x * b.x - a.y * b.y,a.x * b.y + a.y * b.x);//复数运算
}
complex operator + (complex a,complex b) {
return complex(a.x + b.x,a.y + b.y);
}
complex operator - (complex a,complex b) {
return complex(a.x - b.x,a.y - b.y);
} void FFT(complex *a,int n,int ty) {
if(n == 1) return;
complex a1[n >> 1],a2[n >> 1];
for(int i = 0;i <= n;i += 2) {
a1[i >> 1] = a[i];a2[i >> 1] = a[i + 1];//按奇偶分开
}
FFT(a1,n >> 1,ty);FFT(a2,n >> 1,ty);//递归
complex w1 = complex(cos(2.0 * pi / n),ty * sin(2.0 * pi / n));//主n次单位根
complex w = complex(1.0,0.0);//当前的n次单位根
int k = n >> 1;
for(int i = 0;i < k;++i) {//根据式子计算
complex t = w * a2[i];
a[i + k] = a1[i] - t;
a[i] = a1[i] + t;
w = w * w1;
} }
int main() {
int n = read(),m = read();
for(int i = 0;i <= n;++i) A[i].x = read();
for(int i = 0;i <= m;++i) B[i].x = read();
int tot = 1;
while(tot <= n + m) tot <<= 1;
FFT(A,tot,1);
FFT(B,tot,1);
for(int i = 0;i <= tot;++i) A[i] = A[i] * B[i];
FFT(A,tot,-1);
for(int i = 0;i <= n + m;++i)
printf("%d ",(int)(A[i].x / tot + 0.5));
return 0;
}

非递归


/*
* @Author: wxyww
* @Date: 2019-02-01 21:50:51
* @Last Modified time: 2019-02-01 22:03:10
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<ctime>
#include<cmath>
#include<bitset>
using namespace std;
typedef long long ll;
const int N = 3000100;
const double pi = acos(-1.0);
ll read() {
ll x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
return x*f;
}
struct complex {
double x,y;
complex() {x = y = 0;}
complex(double xx,double yy) {
x = xx;y = yy;
}
}A[N],B[N];
complex operator * (complex a,complex b) {
return complex(a.x * b.x - a.y * b.y,a.x * b.y + a.y * b.x);//复数运算
}
complex operator + (complex a,complex b) {
return complex(a.x + b.x,a.y + b.y);
}
complex operator - (complex a,complex b) {
return complex(a.x - b.x,a.y - b.y);
} void FFT(complex *a,int n,int ty) {
for(int i = 0,j = 0;i < n;++i) {//找到最终对应的位置
if(i < j) swap(a[i],a[j]);
for(int k = n >> 1;(j ^= k) < k;k >>= 1);
}
for(int m = 2;m <= n;m <<= 1) {
complex w1 = complex(cos(2*pi/m),ty * sin(2 * pi / m));
for(int i = 0;i < n;i += m) {
complex w = complex(1,0);
for(int k = 0;k < (m >> 1);++k) {
complex t = w * a[i + k + (m >> 1)];
complex u = a[i + k];
a[i + k] = u + t;
a[i + k + (m >> 1)] = u - t;
w = w * w1;
}
}
}
}
int main() {
int n = read(),m = read();
for(int i = 0;i <= n;++i) A[i].x = read();
for(int i = 0;i <= m;++i) B[i].x = read();
int tot = 1;
while(tot <= n + m) tot <<= 1;
FFT(A,tot,1);
FFT(B,tot,1);
for(int i = 0;i <= tot;++i) A[i] = A[i] * B[i];
FFT(A,tot,-1);
for(int i = 0;i <= n + m;++i)
printf("%d ",(int)(A[i].x / tot + 0.5));
return 0;
}

时间差异

最后来一份递归代码与非递归代码运行时间的比较

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

  1. 快速傅里叶变换FFT

    多项式乘法 #include <cstdio> #include <cmath> #include <algorithm> #include <cstdlib ...

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

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

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

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

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

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

  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. spring boot中常用的配置文件的重写

    @Configuration public class viewConfigSolver extends WebMvcConfigurerAdapter { /* spring boot 已经自动配置 ...

  2. hive条件过滤

    where 过滤 %代表任意个字符,_代表一个字符; \\ 转移字符.\\_代表下划线

  3. Excel文件读取的两种方式

    1.Pandas库的读取操作 from pandas import read_excel dr=read_excel(filename,header) dr#dataframe数据 dw=DataFr ...

  4. CSS自定义属性expression_r

    CSS的出现使网页制作者在对网页元素的控制方便许多,当然,有利必有弊,CSS只能对颜色.大小.距离等静态样式有效,对于要实现某些html元素的动态样式就显得有些力不从心.有了CSS的自定义属性expr ...

  5. 在linux命令下访问url

    1.elinks - lynx-like替代角色模式WWW的浏览器 例如: elinks --dump http://www.baidu.com 2.wget 这个会将访问的首页下载到本地 [root ...

  6. How to vi

    h:left,j:down,k:up,l:right.wq #write and quitx #cut one letterdd#cut one line/ #searchs/a/b/ #replac ...

  7. memcached安装报错 error while loading shared libraries: libevent-2.0.so.5: cannot open shared object file: No such file or directory解决

    我是从其他服务器scp来的memcached(~~~整个文件夹的那种,windows用多了的后遗症) 在准备运行 ./memcached -d -u root -l localhost -m 800 ...

  8. 大佬RQY真的强

    今天听了大佬RQY的讲话,做一下总结吧(这里就不吹了,dalao不需要吹) 第一点,基础真的很重要.什么是基础呢?就是你今年学的觉得难到爆啊什么鬼题啊这是我学过的最zz的东西啊怎么会有这种东西啊&am ...

  9. Machine Schedule POJ - 1325(水归类建边)

    Machine Schedule Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 17457   Accepted: 7328 ...

  10. 基于FPGA的UART协议实现(通过线性序列机)

    //////////////////2018/10/15 更新源代码: 实现uart这东西其实早就写了,不过不太完善,对于一个完美主义者来说,必须解决掉它. 1.什么是UART?        通用异 ...