FFT详解——深层次理解FFT

首先感谢Duan2baka大佬,给我讲的非常透彻,大家如果没看懂我写的可以看一下他的—>点这里

概念

咱们先来了解一些非常重要的概念(好吧其实我只是凑字,但了解一下也是很好的)

FFT

多项式函数


卷积

大家可能会想,这三个毫不相干的概念有什么联系呢?

其中,多项式乘法可以用卷积来表示。


事实上,在oi中,卷积都可以用FFT优化。

下面我们来看一下,FFT是怎么优化卷积的(为了方便起见,我们探究多项式乘法)

暴力

大家应该都会,多项式与多项式相乘,先用一个多项式的每一项与另一个多项式的每一项相乘,再把所得的积相加 (初中时背的滚瓜烂熟)

用式子可以表示为


其中


是卷积的常见形式,大家以后如果看见这样的柿子就不要多想,尽情地FFT吧微笑

好了,我相信大家O()的算法都会,我就不多说了,下面我要讲的是O()的优化

预备知识

复数

话不多说,直接上


补充一下,

复数是无序的,但也有四则运算,

大家这么聪明,我相信都会

那好吧,就讲一下

每个复数都可以表示成为复平面上的一个点(也可以说是一个向量)

其中与x轴的夹角称为福角

到x轴距离称为模长

先来一张图


同时,复数的运算法则和实数是一样的(:那你还说一大堆废话干什么:)

但是!复数的乘法却有其独特的意义

我们设 复数z1=a1+b1*i,z2=a2+b2*i

所以z1*z2=(a1+b1*i)*(a2+b2*i)

展开后就变成了 a1*a2-b1*b2+(a1*b2+a2*b1)*i
根据复数在复平面上的意义可知

复数*复数,模长相乘,幅角相加

单位根

一个复数W,满足,那么我们就说w是n次单位根,记作

(特别提醒一下,表示第k个单位根,我学的时候纠结半天)

易证n次单位根共有n个

那么我们看一下4次单位根在复平面上的表示


8次单位根


此处借用duan2baka大佬两张图

很好,你们现在可以想一下三角函数。是不是很像?

so,我们有了两个重要的定理


注意负号

解释一下,第一个就是左右等式分单位圆的比例是一样的

第二个左右等式关于原点对称

很好,这两个定理对后面推结论很有帮助

多项式的表达方法

系数表达法

就是平时数学写的,在此不赘述

点值表达法

我们把n+1不同个点带入n次多项式,得到点集S={p1,p2,...,pn+1},这个点集就是多项式的点值表达法。

为什么要学点值表达法?

当两个多项式相乘时,设这两个多项式的点集分别为S1,S2

如果S1,S2中代入的点的横坐标全部相同,那么我们就可以把这些纵坐标乘起来作为新多项式的点

而点值相乘时间复杂度是O(n)的,这样我们就可以快速相乘。但是我们需要把n+1个点带入,这个的时间复杂度是O的,我们需要找一种能在更短时间内求出n个点值的方法

下面进入正题

FFT

FFT有三个过程,求值(DFT),相乘,插值(IDFT)

我们先来看求值

当然,我们可以代入任何n+1个值,但是我们需要加速乘法,就需要寻找n+1个之间有关系的值代入,这样看看能不能简化运算

推公式时间到

设A(X)为多项式,则


此处再次借用duan2推导(我打不出来这种公式)

我们发现,知道A0和A1,我们就能推出A()和A(),我们称其为蝴蝶变换

所以一直递归下去就好了,时间复杂度O(n log n )

注意由于最后是按奇偶分类,所以我们的多项式需要补齐到位,补齐的位系数为0就可以了

但我们发现,递归时间复杂度太大,所以我们有了迭代的写法

我们先看一下递归是怎么进行的






我们发现,在递归的最后,被放在了它二进制颠倒的位置

所以我们先求出最后一行,再往上迭代

二进制翻转rader代码如下

void rader(Complex* a,int len)
{
	int j=len>>1;
	for(int i=1;i<len-1;i++)
	{
		if(i<j)swap(a[i],a[j]);
		int k=len>>1;
		while(j>=k)
		{
			j-=k;
			k>>=1;
		}
		if(j<k)j+=k;
	}
}
不懂的—> 传送门

迭代写法

void fft(Complex* a,int len,int on)
{
	rader(a,len);
	for(int ll=2;ll<=len;ll*=2)
	{
		double ang=on*2*PI/ll;
		Complex wn(cos(ang),sin(ang));
		for(int i=0;i<len;i+=ll)
		{
			Complex w(1.0,0.0);
			for(int j=i;j<i+ll/2;j++)
			{
				Complex u=a[j];
				Complex t=w*a[j+ll/2];
				a[j]=u+t;
				a[j+ll/2]=u-t;
				w=w*wn;
			}
		}
	}
	if(on==-1)
	{
		for(int i=0;i<len;i++)
		a[i].real/=len;
	}
}

细心的同学可能发现了,这里面的on很奇怪,这就是我们讲的下一个内容——插值

插值

证明

我们发现,只要把换成,就是对虚部取相反数,结果再除以len,就相当于插值了(这太好了)

代码同上,DFT on=1,IDFT on=-1

FFT到这里就讲完了!

————————————

是不是很爽

来两道例题


某c姓童鞋:这题我压位高精一样ac!

那如果改成1e5呢?

唉??这不就是FFT裸题吗???

来一波,最后四舍五入,别忘了进位

ac code


我们可以生成函数,则


这不就是卷积吗??

fft直接上

——————————————

好了FFT讲完了,这也是我的第一篇blog,希望大佬多多包涵,也希望指出本文的错误,一起进步!

猜你喜欢

转载自blog.csdn.net/richard__luan/article/details/80935412
FFT
今日推荐