FFT [TPLY]

题目链接

https://www.luogu.org/problemnew/show/1919

https://www.luogu.org/problemnew/show/3803

资料推荐

orz大佬博客

http://www.cnblogs.com/cjoieryl/p/8206721.html (orz YL大佬)

http://blog.csdn.net/iamzky/article/details/22712347 (超级易懂)

知识点

复数:

https://baike.baidu.com/item/复数/254365?fr=aladdin

单位根(单位复数根)

https://baike.baidu.com/item/单位根

多项式表示

http://blog.csdn.net/acdreamers/article/details/39005227

卷积

http://blog.csdn.net/bitcarmanlee/article/details/54729807

书籍推荐

ACM/ICPC 算法基础训练教程 7.4快速傅里叶变换

算法导论 30章多项式与快速傅里叶变换

觉得看不懂很正常,知识点很抽象需要逐步理解

而对于本文,本文作用为对两位大佬内容解读并且加入自己看法

读者可以先花时间阅读以上推荐的文字,产生一定理解再读本文.由于作者很弱,可能会产生错误,还请大神帮忙指出.

正文

Part1.初识FFT

作用:求多项式乘法的系数(就是初中学的多项式)

FFT多项式乘法与普通形式有差异

一般多项式乘多项式法则:

多项式与多项式相乘,先用一个多项式的每一项与另一个多项式的每一项相乘,再把所得的积相加。举一个例子由多项式乘多项式法则可以得到(a+b)(c+d)=a(c+d)+b(c+d)=ac+ad+bc+bd

FFT'绕圈'法

FFT处理多项式相乘,先从多项式最普通的表达形式---系数表达形式转化成点值表达形式再进行点值乘法.最后,再将点值乘法的结果转回系数表达形式.

而从-系数表达形式转化成点值表达形式的过程,我们叫做DFT,即离散傅里叶变换.

点值乘法的结果转回系数表达形式的步骤,我们称之为离散傅里叶变换的逆运算,也叫插值.

下面再对一些可能不理解的概念进行解释

1~对于点值表达式我的理解 :

点值表达形式就是用若干函数上的点的坐标来表示该函数

比如 f(x)=\(x^2\)-x+2={ ( 0 , 2 ) , ( 1 , 2 ) , ( 2 , 4 ) }

但是这些点的数量一定不小于系数的个数n

这是一个常识

就像求解n元方程一定要有n组方程才能得解

2~点值表达式的计算:

假设我们现在有用点乘表示的多项式

A(x)={(0,1),(1,2),(2,7)}

B(x)={(0,2),(1,2),(2,4)}

[P.S.:点乘表达式能运算的条件是两个点要有相同的x值]

C(x)={(0,2×1),(1,2×2),(2,7×4)}={(0,2),(1,4),(2,28)}

FFT流程图如下:(借鉴他人博客)

part2.复数

0.为什么要学习复数?

YL大佬原话:"学习复数,所有这些七七八八的定理,都是为了FFT的奇妙变换!"

所以,为了FFT,好好学习复数吧!(再一次ORZ YL大佬)

1.复数的定义:我们把形如a+bi(a,b均为实数)的数称为复数,其中a称为实部,b称为虚部,i称为虚数单位,i的值为\(\sqrt{-1}\)

2.单位根的定义:数学上,n次单位根是n次幂为1的复数。

单位根的概念不理解很正常,下面举个例子(来自百度百科)

单位的一次根有一个:1

单位的二次根有两个:+1和-1。

单位的三次根是:

{$ 1 $, \(\frac{-1+\sqrt{3}i}{2}\),\(\frac{-1-\sqrt{3}i}{2}\)}

咱们证明一下

\((\frac{-1+\sqrt{3}i}{2})^3\)=\((\frac{-1+\sqrt{3}i}{2})^2\)×\((\frac{-1+\sqrt{3}i}{2})\)

....................=\((\frac{1+3i^2-2\sqrt{3}i}{4})\)×\((\frac{-1+\sqrt{3}i}{2})\)

....................=\((\frac{-2-2\sqrt{3}i}{4})\)×\((\frac{-1+\sqrt{3}i}{2})\)

....................=\((\frac{(-2)×(1+\sqrt{3}i)×(\sqrt{3}i-1)}{8})\)

....................=-\((\frac{1}{4})\)×(\(3i^2-1\))

....................=\(-(\frac{1}{4})\)*(-4)

....................=1

所以\(\frac{-1+\sqrt{3}i}{2}\)是单位的三次根

单位的四次根是{1,+i,-1,-i}

希望你已经理解了什么是单位复数根(单位根),咱们继续

强烈建议学习一下著名的欧拉公式,后面会用到。

单位复数根定义:对于w\(_n\)=\(e^{2πi/n}\)我们称w\(_n\)为n次单位根(前面讲了n次单位跟哦)。

下面有一个定理需要记住(YL大佬的忠告)

PS由于太弱不会证明,YL大佬的博客上由详细证明可以参考

(w上标表示指数)

消去引理

$$\omega{dk}_{dn}=\omega{k}_{n}$$

这个引理非常非常重要哦,你后面一定一定会见到!

非常,非常重要!记住!

这些复杂的数学公式可能会使你觉得枯燥无味,但为后面的运算奠定了重要的基础,了解一下下啦!

Part3.DFT与-DFT进阶

在本章内于括号里面的数为什么是\(\omega^{k}_{n}\)而不是x,你要到FFT才能明白.但本章内容中的\(\omega^{k}_{n}\)你可以当成x看(下一章就不行了)

DFT

对于k=0~n-1,定义:

\[y_k=A(\omega^{k}_{n}) = \Sigma^{n-1}_{j=0} a_j(\omega^{k}_{j})^j
\]

对于这个式子,你这么看\(y_k=A(x)=\Sigma^{n-1}_{j=0} a_j(x)^j\)

我们知道DFT求的是点乘

点乘就是坐标

平时你是怎么求坐标的?

选定一个x值,把x值带到里面求出y

上面这个式子就是这么做的啦(还不懂就再看看多项式的系数表达式)

所以我们继续分析,对于每一个x,我们都要O(n)地将值带到多项式里求值,我们最少要n个坐标,所以总复杂度是O(n*n)

最后我们将得到的y称为a的离散傅里叶变换,记作\(y=DFT_n(a)\) \((这里的y,a指的是所有的y_k,a_k(k有n种,也就是要取k个))\)

这个定义先记着,还要注意的是,这一步的作用是将系数表达形式变成点乘表达形式\(O(n^2)\).

再用之前的点乘算法算出点乘的结果O(n)(Part1.讲了点乘,复杂度证明很显然).

最后用下面的离散傅里叶逆变换(我叫它-DFT),再换回去\(O(n^2)\)所以总复杂度为\(O(n^2)\)

-DFT

我这里直接摆出结果,有兴趣的可以自己证明一下

\[对于y_k = \Sigma^{n-1}_{i=0}a_i(x)^i
\]

\[有a_k = \frac{1}{n}\Sigma^{n-1}_{i=0}y_i(x)^i
\]

再对比DFT公式

\[y_k = \Sigma^{n-1}_{j=0} a_j(x)^j
\]

可以发现,DFT-DFT的差别很小,基本上只是y,a调换了一下罢了(也有不同的...)

Part4 FFT水落石出

终于来到了最后,前面所有令人头大的背景知识,都是为了这一章而服务.

所以这一章是至关重要的.

如果前面还没有完全理解,请重新巩固扎实.

网上都说,FFT的复杂度为O(nlgn)

那是怎么来的呢?

答案是:神奇的分治

先把A(x)这个多项式拆分成奇数项与偶数项

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

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

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

这个是正确的,希望读者拿起草稿纸自己演算一遍

咱们把这个多项式拆分成这样子以后

复数闪亮登场

开始用复数的公式了哦

咱们不妨把\(\omega^{k}\)带入到x中去

\[A(\omega^k_n)=A^{[0]}((\omega^k_n)^2) + \omega^k_n A^{[1]}((\omega^k_n)^2)
\]

然后,通过之前提到过的单位复数根消去引理我们可以得到(说了消去引理非常重要)

咱们用消去引理把\((\omega^k_n)^2\)化一下

\((\omega^k_n)^2\)=\(\omega^{2k}_n\)

.............=\(\omega^{\frac{2k}{2}}_{\frac{n}{2}}\)

.............=\(\omega^{k}_{\frac{n}{2}}\)

又因为$$\omega{\frac{n}{2}}_{n}=\omega_{2}=e{k\pi i}= cos k\pi + i sin k\pi = -1(有名的欧拉公式)$$

所以

$$\omega{k+\frac{n}{2}}_n=\omega{k}_n*\omega{\frac{n}{2}}_n=-\omega{k}_n$$

所以

\[A(\omega^{k+\frac{n}{2}}_n)=A^{[0]}((\omega^{k+\frac{n}{2}}_n)^2) + \omega^{k+\frac{n}{2}}_nA^{[1]}((\omega^{k+\frac{n}{2}}_n)^2)
\]

\[=A^{[0]}((-\omega^k_{n})^2)-\omega^k_{n}A^{[1]}((-\omega^k_{n})^2)
\]

\[=A^{[0]}(\omega^{2k}_{n})-\omega^k_{n}A^{[1]}(\omega^{2k}_{n})
\]

\[再一次用到咱们的消去引理
\]

\[=A^{[0]}(\omega^k_{\frac{n}{2}})-\omega^k_{n}A^{[1]}(\omega^k_{\frac{n}{2}})。。。(式1)
\]

然而我们同时也可以暴力把原式化一下:

\[A(\omega^k_n)=A^{[0]}((\omega^k_n)^2) + \omega^k_n A^{[1]}((\omega^k_n)^2)
\]

\[=A^{[0]}(\omega^{2k}_n) + \omega^k_n A^{[1]}(\omega^{2k}_n)
\]

\[又因为消去引理
\]

\[=A^{[0]}(\omega^{k}_{\frac{n}{2}}) + \omega^k_n A^{[1]}(\omega^k_{\frac{n}{2}})。。。(式2)
\]

蝴蝶操作

所以我们把我们推了这么久的(式1)和(式2)拿出来并排看

\[A(\omega^k_n)=A^{[0]}(\omega^{k}_{\frac{n}{2}}) + \omega^k_n A^{[1]}(\omega^k_{\frac{n}{2}})
\]

\[A(\omega^{k+\frac{n}{2}}_n)=A^{[0]}(\omega^k_{\frac{n}{2}})-\omega^k_{n}A^{[1]}(\omega^k_{\frac{n}{2}})
\]

然后我们发现

用$$A{[0]}(\omega{k}{\frac{n}{2}}) 和 A{[1]}(\omegak{\frac{n}{2}}) 和\omega^k_{n}$$

可以同时算出$$A(\omegak_n)和A(\omega{k+\frac{n}{2}}_n)$$

计算量便减小了一半

我们管这叫蝴蝶操作

蝴蝶操作流程如下

然后我们还可以发现

蝴蝶操作无非就是把奇数项和偶数项合并算出新项再往上继续往上合并直到我们得到最终的式子

也就是这样



是可以递归着做

不断分离奇数项偶数项最后再回溯合并

这是由上而下

那么能不能由下而上呢?

我们看最下层叶子节点数字的编号,通过找规律我们发现每个数和他二进制相反的位置互换

举个例子

4 -> 100

1 -> 001

所以4和1交换

(注意标号从0开始)

接下来。。。

我承认我蝴蝶操作讲的不清楚

由于知识有限只能讲到这里

网上关于蝴蝶操作的好的文章有很多。

大家可以去看一看

下面是一些例题

(链接再最上面)

可以把它当成板子背一背

代码

//P3803 【模板】多项式乘法(FFT)
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <cmath> #define rg register int
#define ll long long
#define db double
#define il inline #define INF 2147483647
#define SZ 10000001 using namespace std; const double pi=acos(-1); struct Complex
{
db r,i;
Complex(){}
Complex(db a,db b):r(a),i(b) {}
Complex operator+(const Complex B)const
{return Complex(r+B.r,i+B.i);}
Complex operator-(const Complex B)const
{return Complex(r-B.r,i-B.i);}
Complex operator*(const Complex B)const
{return Complex(r*B.r-i*B.i,r*B.i+i*B.r);}
};
Complex X,Y,a[SZ],b[SZ]; int r[SZ],n,m,l;
il void FFT(Complex *a,int x)
{
for(rg i=0;i<n;++i) if(i<r[i]) swap(a[i],a[r[i]]);
for(rg i=1;i<n;i<<=1)
{
Complex wn(cos(pi/i),x*sin(pi/i));
for(rg j=0;j<n;j+=(i<<1))
{
Complex w(1,0);
for(rg k=0;k<i;++k,w=w*wn)
{
X=a[j+k],Y=a[i+j+k]*w;
a[j+k]=X+Y,a[i+j+k]=X-Y;
}
}
}
if(x==-1) for(rg i=0;i<n;++i) a[i].r=a[i].r/n;
} int main()
{
scanf("%d%d",&n,&m);
for(rg i=0;i<=n;++i) scanf("%lf",&a[i].r);
for(rg i=0;i<=m;++i) scanf("%lf",&b[i].r);
m+=n;for(n=1;n<=m;n<<=1) ++l;
for(rg i=0;i<n;++i) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
FFT(a,1);FFT(b,1);
for(rg i=0;i<=n;++i) a[i]=a[i]*b[i];
FFT(a,-1);
for(rg i=0;i<=m;++i) printf("%d ",(int)(a[i].r+0.5));
return 0;
}
P1919 【模板】A*B Problem升级版(FFT快速傅里叶)
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <cmath> #define rg register int
#define ll long long
#define il inline
#define ldb long double #define INF 2147483647
#define SZ 1000001 using namespace std;
const ldb pi=acos(-1); struct Complex
{
ldb r,i;
Complex(){}
Complex(ldb a,ldb b):r(a),i(b){}
il Complex operator+(const Complex B)const
{return Complex(r+B.r,i+B.i);}
il Complex operator-(const Complex B)const
{return Complex(r-B.r,i-B.i);}
il Complex operator*(const Complex B)const
{return Complex(r*B.r-i*B.i,r*B.i+i*B.r);}
};
Complex X,Y,a[SZ],b[SZ];
int n,m,l,r[SZ];
il void FFT(Complex *a,int x)
{
for(rg i=0;i<n;++i) if(i<r[i]) swap(a[i],a[r[i]]);
for(rg i=1;i<n;i<<=1)
{
Complex wn(cos(pi/i),x*sin(pi/i));
for(rg j=0;j<n;j+=(i<<1))
{
Complex w(1,0);
for(rg k=0;k<i;++k,w=w*wn)
{
X=a[j+k],Y=a[i+j+k]*w;
a[j+k]=X+Y,a[i+j+k]=X-Y;
}
}
}
if(x==-1) for(rg i=0;i<n;++i) a[i].r=a[i].r/n;
} char ch1[SZ],ch2[SZ];
int res[SZ];
int main()
{
scanf("%d",&n);
cin>>ch1;
cin>>ch2;
for(rg i=0;i<n;++i)
{
a[i].r=ch1[n-i-1]-'0';
b[i].r=ch2[n-i-1]-'0';
}
m=n+n-2;for(n=1;n<=m;n<<=1) ++l;
for(rg i=0;i<n;++i) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
FFT(a,1);FFT(b,1);
for(rg i=0;i<=n;++i) a[i]=a[i]*b[i];
FFT(a,-1);
for(rg i=0;i<=m;++i) res[i]=(int)(a[i].r+0.5);
for(rg i=0;i<=m;++i) res[i+1]+=res[i]/10,res[i]%=10;
rg top=m+1;
while(res[top]>9)
{
res[top+1]=res[top]/10;
res[top]%=10;
++top;
}
while(!res[top]) --top;
for(rg i=top;i>=0;--i)
printf("%d",res[i]);
return 0;
}

几点注意

pi 最好不要手写(当然如果你能背到小数点后十几位我也不栏你)

不然很可能出精度问题

最好手写Complex速度快很多

注意一些从0开始的地方

加油哦!

END

FFT [TPLY]的更多相关文章

  1. 并行计算提升32K*32K点(32位浮点数) FFT计算速度(4核八线程E3处理器)

    对32K*32K的随机数矩阵进行FFT变换,数的格式是32位浮点数.将产生的数据存放在堆上,对每一行数据进行N=32K的FFT,记录32K次fft的时间. 比较串行for循环和并行for循环的运行时间 ...

  2. 【BZOJ-2179&2194】FFT快速傅里叶&快速傅里叶之二 FFT

    2179: FFT快速傅立叶 Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 2978  Solved: 1523[Submit][Status][Di ...

  3. 为什么FFT时域补0后,经FFT变换就是频域进行内插?

    应该这样来理解这个问题: 补0后的DFT(FFT是DFT的快速算法),实际上公式并没变,变化的只是频域项(如:补0前FFT计算得到的是m*2*pi/M处的频域值, 而补0后得到的是n*2*pi/N处的 ...

  4. FFT NNT

    算算劳资已经多久没学新算法了,又要重新开始学辣.直接扔板子,跑...话说FFT算法导论里讲的真不错,去看下就懂了. //FFT#include <cstdio> #include < ...

  5. CC countari & 分块+FFT

    题意: 求一个序列中顺序的长度为3的等差数列. SOL: 对于这种计数问题都是用个数的卷积来进行统计.然而对于这个题有顺序的限制,不好直接统计,于是竟然可以分块?惊为天人... 考虑分块以后的序列: ...

  6. ECF R9(632E) & FFT

    Description: 上一篇blog. Solution: 同样我们可以用fft来做...就像上次写的那道3-idoit一样,对a做k次卷积就好了. 同样有许多需要注意的地方:我们只是判断可行性, ...

  7. fft练习

    数学相关一直都好弱啊>_< 窝这个月要补一补数学啦, 先从基础的fft补起吧! 现在做了 道. 窝的fft 模板 (bzoj 2179) #include <iostream> ...

  8. FFT时域与频域的关系,以及采样速率与采样点的影响

    首先对于FFT来说,输入的信号是一个按一定采样频率获得的信号序列,而输出是每个采样点对应的频率的幅度(能量). 下面详细分析: 在FFT的输出数据中,第一个值是直流分量的振幅(这样对应周期有无穷的可能 ...

  9. 【玩转单片机系列002】 如何使用STM32提供的DSP库进行FFT

    前些日子,因为需要在STM32F103系列处理器上,对采集的音频信号进行FFT,所以花了一些时间来研究如何高效并精确的在STM32F103系列处理器上实现FFT.在网上找了很多这方面的资料做实验并进行 ...

随机推荐

  1. Maven项目中的配置文件找不到以及打包问题

    我们在使用maven开发项目的时候,创建model的时候会自动创建一个resource目录:这个目录是用来放配置文件的,我们把配置文件放在这个目录中项目打包才会将配置文件打包进去. 有的时候我们会把配 ...

  2. javascript 模块

    一.模块 function foo() { var something = "cool"; var another = [1, 2, 3]; function doSomethin ...

  3. Halcon一日一练:图像分辨率与像素

    1.图像像素: 像素是指由图像的小方格即所谓的像素(pixel)组成的,这些小方块都有一个明确的位置和被分配的色彩数值,而这些一小方格的颜色和位置就决定该图像所呈现出来的样子.像素是构成图像的基本单元 ...

  4. css的浮动与定位

    显示与隐藏 标签 属性 值 效果 区别 css的style display none 元素不可见 不占页面空间 css的style visibility hidden 元素不可见 占页面空间 disp ...

  5. 开启Nginx的目录文件列表功能

    ngx_http_autoindex_module  此模块用于自动生成目录列表,ngx_http_autoindex_module只在 ngx_http_index_module模块未找到索引文件时 ...

  6. Ubuntu 配置FTP服务器

    第三方的文件传输软件用着很不爽,想着自己搭建一个FTP来干活. 首先检查是否已经安装了FTP,输入命令: vsftpd -v  可以查看版本,如果没有安装,无法执行. 安装FTP p.p1 { mar ...

  7. 对html语义化的理解

    所有人都知道html即超文本标记语言或超文本链接标示语言,是目前网络上应用最为广泛的语言,也是构成网页文档的主要语言. html标签中的大部分都是由"语义化"标签所担任 那么,它有 ...

  8. ActiveRecord的生命周期

    ActiveRecord的生命周期,通过方法重写和插入我们需要的业务逻辑来达到我们对程序的控制. 示例: 1,beforeSave() public function beforeSave($inse ...

  9. CodeForces - 681A A Good Contest

    咳咳,从今天开始,每天做一个英语题,不论简单还是难,坚持到下学期的省赛,希望能有效果. 这题就是判断是否能成为red,如果他超越的人里面有在比赛前分数达到2400,并且在比赛后分数上升,那么他就能成为 ...

  10. 情景linux--shell如何实现多线程?

    情景linux--shell如何实现多线程? 情景 shell脚本的执行效率虽高,但当任务量巨大时仍然需要较长的时间,尤其是需要执行一大批的命令时.因为默认情况下,shell脚本中的命令是串行执行的. ...