声明:本FFT是针对OI的。专业人员请出门左拐。

前言

很久以前,我打算学习FFT。

然而,算法导论讲的很详细,却看不懂。网上博客更别说了,什么频率之类的都来了。我暗自下了决心:写一篇人看得懂的FFT

今天讲了FFT,我对这精(xuan)妙(xue)的算法有了初步的认识。我想,我如愿以偿了。

由于此算法不好理解的主要原因是涉及大量数学知识,所以我会尽量讲的浅显一点,以会写代码为目的。部分高超的证明会略去。

算法简介

快速傅里叶变换,FFT(Fast-Fast TLE,Fast Fourier Transform),是一种高效的多项式系数点值互换的算法(不懂没关系,一会儿要讲)。最广泛的应用是计算多项式乘法。

通常,暴力展开复杂度为O($n^{2}$)。而FFT则可以达到O(nlogn)

辣么我们开始吧。

前置知识(顺便简单提一下流程,后面流程详细讲)

1.多项式

回忆一下初中多项式的定义:

由若干个单项式相加组成的代数式叫做多项式。

单项式又是啥?

由数或字母的积组成的代数式叫做单项式。

low爆了!

我们已经是高中生了什么时候的事我怎么不知道,我们换一种定义:

多项式   :=   f(x)=$\sum_{i=0}^{n-1}a_{i}x^{i}\qquad$

其中n称为次数。

怎么样?有没有莫比乌斯反演的既视感?

2.多项式的表示方法

上面提到的叫做系数表达式。其实多项式还有一种表示:点值表达式。

标准定义找不到啊qwq

那这么说吧:

先把多项式看做一个函数,画出它的图像

然后在上面任取n个不同的点

那么这三个点就可以唯一的确定这个多项式。至于为什么唯一,通过意识流很容易证明。

为什么要引入这个东西呢?因为如果把两个多项式转换为x相同的点值,就可以O(n)求出它们的乘积。太神奇啦!

辣么现在问题就成了:如何把原多项式转成点值,再把乘出来的点值转系数。

最直观的方法:把原多项式转成点值就取几个值,点值转系数可以高斯消元。

于是尴尬的事情发生了:

总复杂度O($n^{3}$)

看来还要想办法啊。

3.弧度制和任意三角函数(学过的往后跳)

初中的三角函数是针对锐角的。那其他角有没有三角函数呢?

当然啦。

这要从任意角说起。

高中我们定义角为一条射线(一般指x正半轴)逆时针旋转后(称为终边)与原来的射线形成的图形,允许有负数,即顺时针

所以,这是个角:

这也是个角,而且和上一个不同:

这还是个角,而且是负角:

这仍然是个角:

好吧。。。

以前用角都是有单位,即1°=一个周角的1/360

那能不能换一个没单位的呢?

定义弧度为这个角所对弧长与半径的比,这样角就是一个常数。

很容易知道,一个周角是2π弧度。即π弧度对应180°。(前面那个是圆周率  垃圾markdown)

这样任意角三角函数就出来了:

在直角坐标系中,以O为圆心作一个半径为R的圆。角α的终边与圆的交点坐标(x,y),那么有

sinα=$\frac{x}{R}$

cosα=$\frac{y}{R}$

其他三角函数类似,但FFT用不到,就不说了

4.复数与单位根

实数貌似没什么特殊性质。那实数之外呢?

我们定义$i^{2}$=-1。任何复数(也就是目前的所有数)都可以表示成x+yi的形式(x,y均为实数)

等等!这不是没有意义吗?这是什么鬼玩意儿?

好吧,你可以不管它。就把它看成这个:

 struct complex
{
double x,y;
};

就好了。

当然它的乘法就是把它拆开,$i^{2}$换成-1,i保留就好了。(不懂没关系,后面看代码)

由于是两个实数,所以我们可以yy出一个平面:x代表实部,y代表虚部。这上面的点都是与复数一一对应的。

单位根就是满足$w^{n}$=1的所有复数,共n个,记为$w_{n}$(注:这里的w应该是个希腊字母,但为了偷懒就用w代替)

根据意识流在复平面上,这n个点与原点连线长度相同,且平分圆周。

并且,它们是$w_{n}$,$w_{n}^{2}$,$w_{n}^{3}$......$w_{n}^{n}$的关系,我们就这么表示好了

你可以用上面的性质把n个单位根算出来

即$w_{n}^{k}$=cos$\frac{2πk}{n}$+sin$\frac{2πk}{n}$i

Ⅳ算法流程

我们假装n是2的k次幂 即$n=2^{k}$,k为非负整数

令x=$w_{n}^{k}$,代入多项式

(注:1.如果你不想看,并且你可以在不理解的情况下背代码,可以跳过以下推导  2.以下的i都是循环变量而不是虚数单位)

$F(w_{n}^{k})=\sum_{i=0}^{n-1}a_{i}w_{n}^{ik}$

闲着没事,把它按奇偶分类:

$F(w_{n}^{k})=\sum_{i=0}^{\frac{n}{2}-1}a_{2i}w_{n}^{2ik}+a_{2i+1}w_{n}^{(2i+1)k}$

看后面那坨不顺眼

$F(w_{n}^{k})=\sum_{i=0}^{\frac{n}{2}-1}a_{2i}w_{n}^{2ik}+w_{n}^{k}a_{2i+1}w_{n}^{2ik}$

根据折半引理:

$(w_{n}^{k})^{2}=w_{\frac{n}{2}}^{k}$

可得:

$F(w_{n}^{k})=\sum_{i=0}^{\frac{n}{2}-1}a_{2i}w_{\frac{n}{2}}^{ik}+w_{n}^{k}a_{2i+1}w_{\frac{n}{2}}^{ik}$

有没有一种似曾相识的感觉呢?

所以可以递归下去:(type干啥的马上就知道了)

const double PI=acos(-1.0);
struct complex
{
double x,y;
complex(double x=,double y=):x(x),y(y){}
}a[MAXN],b[MAXN];
complex operator +(const complex& a,const complex& b){return complex(a.x+b.x,a.y+b.y);}
complex operator -(const complex& a,const complex& b){return complex(a.x-b.x,a.y-b.y);}
complex operator *(const complex& a,const complex& b){return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
void fft(int lim,complex *a,int type)
{
if (lim==)
return;
complex a1[lim>>],a2[lim>>];
for (int i=;i<=lim;i+=)
a1[i>>]=a[i],a2[i>>]=a[i+];
fft(lim>>,a1,type);
fft(lim>>,a2,type);
complex wn(cos(2.0*PI/lim),type*sin(2.0*PI/lim)),w(,);
for (int i=;i<(lim>>);i++,w=w*wn)
{
a[i]=a1[i]+w*a2[i];
a[i+(lim>>)]=a1[i]-w*a2[i];
}
}

于是 就完了?

怎么可能?

上面只是系数转点值。而平时点值很少用,即使用也不会是复数啊……

所以还要把点值转系数。我们称为傅里叶逆变换。

又是推式子的时间啦!

(其实这里我也不怎么懂……但结论很简单,直接记就行)

我们设$c_{i}$为多项式在$w_{n}^{-i}$的点值表达式(也就是y值),其中$0<=i<n$LaTeX可以美化呢

  $c_{k}=\sum_{i=0}^{n-1}y_{i}(w_{n}^{-k})^{i}$

把$y_{i}$拆开

  $=\sum_{i=0}^{n-1}(\sum_{j=0}^{n-1}a_{j}(w_{n}^{i})^{j})(w_{n}^{-k})^{i})$

你到前面来

  $=\sum_{i=0}^{n-1}(\sum_{j=0}^{n-1}a_{j}(w_{n}^{j})^{i})(w_{n}^{-k})^{i})$

辣眼睛的小括号

  $=\sum_{i=0}^{n-1}\sum_{j=0}^{n-1}a_{j}(w_{n}^{j})^{i}(w_{n}^{-k})^{i}$

咦?

  $=\sum_{i=0}^{n-1}\sum_{j=0}^{n-1}a_{j}(w_{n}^{j-k})^{i}$

瞎搞一波

  $=\sum_{i=0}^{n-1}a_{j}(\sum_{j=0}^{n-1}(w_{n}^{j-k})^{i})$

设$S(x)=\sum_{i=0}^{n-1}x$

代入

$S(w_{n}^{k})=1+(w_{n}^{k})+(w_{n}^{k})^{2}+... ... +(w_{n}^{k})^{n-1}$

当k不为0时,运用你丰富的等比数列知识

$S(w_{n}^{k})=\frac{(w_{n}^{k})^{n}-1}{(w_{n}^{k})-1}$

$S(w_{n}^{k})=0$

k=0时,显然$S(w_{n}^{k})=n$

回到刚刚的式子,发现:

$=\sum_{i=0}^{n-1}a_{j}(\sum_{j=0}^{n-1}(w_{n}^{j-k})^{i})$

当$j=k$时,为n

当$j \neq k$时,为0

$c_{k}=na_{k}$

$a_{k}=\frac{a_{k}}{n}$

也就是把上面的type改成-1,然后答案除以lim就好了

完整代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cctype>
#define MAXN 2000005
const double PI=acos(-1.0);
using namespace std;
inline int read()
{
int ans=0,f=1;
char c=getchar();
while (!isdigit(c))
{
if (c=='-')
f=-1;
c=getchar();
}
while (isdigit(c))
{
ans=(ans<<3)+(ans<<1)+(c^48);
c=getchar();
}
return f*ans;
}
struct complex
{
double x,y;
complex(double x=0,double y=0):x(x),y(y){}
}a[MAXN],b[MAXN];
complex operator +(const complex& a,const complex& b){return complex(a.x+b.x,a.y+b.y);}
complex operator -(const complex& a,const complex& b){return complex(a.x-b.x,a.y-b.y);}
complex operator *(const complex& a,const complex& b){return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
void fft(int lim,complex *a,int type)
{
if (lim==1)
return;
complex a1[lim>>1],a2[lim>>1];
for (int i=0;i<=lim;i+=2)
a1[i>>1]=a[i],a2[i>>1]=a[i+1];
fft(lim>>1,a1,type);
fft(lim>>1,a2,type);
complex wn(cos(2.0*PI/lim),type*sin(2.0*PI/lim)),w(1,0);
for (int i=0;i<(lim>>1);i++,w=w*wn)
{
a[i]=a1[i]+w*a2[i];
a[i+(lim>>1)]=a1[i]-w*a2[i];
}
}
int main()
{
int n,m;
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 lim=1;
while (lim<=n+m)
lim<<=1;
fft(lim,a,1);
fft(lim,b,1);
for (int i=0;i<=lim;i++)
a[i]=a[i]*b[i];
fft(lim,a,-1);
for (int i=0;i<=n+m;i++)
printf("%d ",(int)(a[i].x/lim+0.5));
return 0;
}

  

 V.改进

什么?你T了?

不会吧我都是RE

那我们换一种实现?

(侵删)

不难看出,原序列和后序列二进制是反转的

然后就可以AC此题

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cctype>
#define MAXN 10000010
const double PI=acos(-1.0);
using namespace std;
inline int read()
{
int ans=0,f=1;
char c=getchar();
while (!isdigit(c))
{
if (c=='-')
f=-1;
c=getchar();
}
while (isdigit(c))
{
ans=(ans<<3)+(ans<<1)+(c^48);
c=getchar();
}
return f*ans;
}
struct complex
{
double x,y;
complex(double x=0,double y=0):x(x),y(y){}
}a[MAXN],b[MAXN];
complex operator +(const complex& a,const complex& b){return complex(a.x+b.x,a.y+b.y);}
complex operator -(const complex& a,const complex& b){return complex(a.x-b.x,a.y-b.y);}
complex operator *(const complex& a,const complex& b){return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
int lim=1;
int l,r[MAXN];
void fft(complex *a,int type)
{
for (int i=0;i<lim;i++)
if (i<r[i])
swap(a[i],a[r[i]]);
for (int mid=1;mid<lim;mid<<=1)//非<=
{
complex wn(cos(PI/mid),type*sin(PI/mid));
for (int R=mid<<1,j=0;j<lim;j+=R)
{
complex w(1,0);
for (int k=0;k<mid;k++,w=w*wn)
{
complex x=a[j+k],y=w*a[j+k+mid];
a[j+k]=x+y;
a[j+k+mid]=x-y;
}
}
}
}
int main()
{
int n,m;
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();
while (lim<=n+m)
lim<<=1,l++;
for (int i=0;i<lim;i++)
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
fft(a,1);
fft(b,1);
for (int i=0;i<=lim;i++)
a[i]=a[i]*b[i];
fft(a,-1);
for (int i=0;i<=n+m;i++)
printf("%d ",(int)(a[i].x/lim+0.5));
return 0;
}

  

全网第二好懂的FFT(快速傅里叶变换)的更多相关文章

  1. FFT 快速傅里叶变换 学习笔记

    FFT 快速傅里叶变换 前言 lmc,ikka,attack等众多大佬都没教会的我终于要自己填坑了. 又是机房里最后一个学fft的人 早背过圆周率50位填坑了 用处 多项式乘法 卷积 \(g(x)=a ...

  2. CQOI2018 九连环 打表找规律 fft快速傅里叶变换

    题面: CQOI2018九连环 分析: 个人认为这道题没有什么价值,纯粹是为了考算法而考算法. 对于小数据我们可以直接爆搜打表,打表出来我们可以观察规律. f[1~10]: 1 2 5 10 21 4 ...

  3. 「学习笔记」FFT 快速傅里叶变换

    目录 「学习笔记」FFT 快速傅里叶变换 啥是 FFT 呀?它可以干什么? 必备芝士 点值表示 复数 傅立叶正变换 傅里叶逆变换 FFT 的代码实现 还会有的 NTT 和三模数 NTT... 「学习笔 ...

  4. matlab中fft快速傅里叶变换

    视频来源:https://www.bilibili.com/video/av51932171?t=628. 博文来源:https://ww2.mathworks.cn/help/matlab/ref/ ...

  5. FFT —— 快速傅里叶变换

    问题: 已知A[], B[], 求C[],使: 定义C是A,B的卷积,例如多项式乘法等. 朴素做法是按照定义枚举i和j,但这样时间复杂度是O(n2). 能不能使时间复杂度降下来呢? 点值表示法: 我们 ...

  6. [C++] 频谱图中 FFT快速傅里叶变换C++实现

    在项目中,需要画波形频谱图,因此进行查找,不是很懂相关知识,下列代码主要是针对这篇文章. http://blog.csdn.net/xcgspring/article/details/4749075 ...

  7. FFT快速傅里叶变换

    FFT太玄幻了,不过我要先膜拜HQM,实在太强了 1.多项式 1)多项式的定义 在数学中,由若干个单项式相加组成的代数式叫做多项式.多项式中的每个单项式叫做多项式的项,这些单项式中的最高项次数,就是这 ...

  8. FFT快速傅里叶变换算法

    1.FFT算法概要: FFT(Fast Fourier Transformation)是离散傅氏变换(DFT)的快速算法.即为快速傅氏变换.它是根据离散傅氏变换的奇.偶.虚.实等特性,对离散傅立叶变换 ...

  9. [学习笔记]FFT——快速傅里叶变换

    大力推荐博客: 傅里叶变换(FFT)学习笔记 一.多项式乘法: 我们要明白的是: FFT利用分治,处理多项式乘法,达到O(nlogn)的复杂度.(虽然常数大) FFT=DFT+IDFT DFT: 本质 ...

随机推荐

  1. Morley's Theorem (计算几何基础+向量点积、叉积、旋转、夹角等+两直线的交点)

    题目链接:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem ...

  2. 【Zigbee技术入门教程-02】一图读懂ZStack协议栈的核心思想与工作机理

    [Zigbee技术入门教程-02]一图读懂ZStack协议栈的核心思想与工作机理 广东职业技术学院  欧浩源   Z-Stack协议栈是一个基于任务轮询方式的操作系统,其任务调度和资源分配由操作系统抽 ...

  3. 第一章:获取服务器服务banner

    #!c:\\perl\\bin\\perl.exe #读取服务器的首行(banner) use IO::Socket; my $service = '121.201.67.177:ssh'; my $ ...

  4. mips64高精度时钟引起ktime_get时间不准,导致饿狗故障原因分析【转】

    转自:http://blog.csdn.net/chenyu105/article/details/7720162 重点关注关中断的情况.临时做了一个版本,在CPU 0上监控所有非0 CPU的时钟中断 ...

  5. centos_7.1.1503_src_2

    farstream02-0.2.3-3.el7.src.rpm 05-Jul-2014 12:59 1.2M   fcoe-utils-1.0.29-9.el7.src.rpm 31-Mar-2015 ...

  6. CNN中千奇百怪的卷积方式大汇总

    1.原始版本 最早的卷积方式还没有任何骚套路,那就也没什么好说的了. 见下图,原始的conv操作可以看做一个2D版本的无隐层神经网络. 附上一个卷积详细流程: [TensorFlow]tf.nn.co ...

  7. atom编辑器插件atom-ternjs

    这是官方文档:https://atom.io/packages/atom-ternjs 官方介绍: JavaScript code intelligence for atom with Tern. A ...

  8. C# 笔记——委托

    委托是一个类型安全的对象,它指向程序中另一个以后会被调用的方法(或多个方法).通俗的说,委托是一个可以引用方法的对象,当创建一个委托,也就创建一个引用方法的对象,进而就可以调用那个方法,即委托可以调用 ...

  9. 简单理解Hash算法的作用

    什么是Hash Hash算法,简称散列算法,也成哈希算法(英译),是将一个大文件映射成一个小串字符.与指纹一样,就是以较短的信息来保证文件的唯一性的标志,这种标志与文件的每一个字节都相关,而且难以找到 ...

  10. [转]nginx启动期都做了哪些事

    nginx是个多进程web容器,不同的配置下它的启动方式也是不同的,这里我只说说最典型的启动方式. 它有1个master进程,和多个worker进程(最优配置的数量与CPU核数相关).那么,首先我们要 ...