FFT—快速傅里叶变换学习小记
来源:互联网 发布:日程软件 知乎 电脑 编辑:程序博客网 时间:2024/05/22 12:32
我们要干什么
FFT能够快速地解决多项式乘法。额···好像也只能干这个事。
一个特殊的例子:高精度乘法。
问题简述
我们记一个多项式
注意最高次系数为n-1,不是n。实际上次数界就是有多少项系数。
两个多项式相乘,我们一般记为
FFT的中心思想
转换思路
普通的相乘方法:
提出概念: 点值,插值。
点值
一个次数界为n的多项式A的点值表达为n个点值对所组成的集合。形如
其中
比如多项式
插值
插值运算为点值运算的逆运算,如果给定一个n个点值对的点值表达,我们可以确定唯一一个次数界为n的多项式。就比如通过上面的三个点,可以求出A(x)的各项系数。
综合两者进行多项式乘法
确定一组x,可以得到A,B的点值表达。
那么可以知道C的点值表达为
通过插值运算,就可以得到多项式C的系数了。
注意:A,B次数界均为n的话,C的次数界为2n-1(为了方便直接弄成2n),所以x集合大小至少为2n,不然插值失败。
流程
对于次数界均为n的两个多项式A,B
1.点值运算,构造长度为2n的点值表达;
2.每个点的y相乘,算出C的点值表达;
3.插值运算,通过点值表达求出系数。
然而暴力来做时间复杂度仍为
其实我们可以通过选特殊的x集合,来优化时间,我们选的数叫做n次单位复数根。
N次单位复数根及其性质
n次单位复数根是满足
这一条是欧拉定理(之一),实际上,我们发现
我们来看个图,直观地发现规律。
可以发现,他们均匀分布在复平面(以实数域为x轴,虚数域i为y轴的平面)的单位圆上。跟学三角函数的时候那些东西是一样的。
性质1,主n次单位根
我们称
n次单位复数根可视为公比为
性质2,群的性质
由于单位根们可表达为一对三角函数,那么他也是具有周期的
而由性质1得,一个
又可以得到
两个引理
消去引理
推论:
折半引理
若n为>0的偶数,那么n次单位复数根的平方的集合,等于n/2次单位复数根的集合。
这个也好理解,可以知道
求和引理
对于任何整数n>=1和不能被n整除的非负整数k(负数可以通过群的性质变为正数),有
等比数列求和,化出来发现末项和首项都是1,所以是0。k为n的时候分母为0,所以不成立。
FFT及其关键算法
DFT——离散傅里叶变换
我们的目标:计算次数界为n的多项式
定义结果y
y即为a的离散傅里叶变换。
可记为
FFT——快速傅里叶变换
为了充分利用n次单位复数根,我们令n为2的幂,这样可以在
分治策略
如何求出单个数x的函数值A(x)?我们定义两个多项式
即一个全是偶数编号的系数,一个全是奇数的。我们发现这两个东西的次数是n/2,变小了一半。
可得
阶段性总结
原问题:求
转化成:求
可以发现不同的自变量少了一半,而每个自变量出现2次。
于是,我们递归地对n/2的多项式
伪代码
看到最后的处理,我们对于
运行时间显然是
这样我们解决了点值运算。可以处理出C的点值表达了!
插值运算
这个其实跟点值运算差不多。
我们如果把点值运算写成矩阵方程的形式,可以得到表达式
只是把
那么可以知道
逆矩阵
定理,
证明很麻烦,考虑证明
证明:
那么,只需要把原来fft里面的a和y互换,
实现的优化
我们不可能像伪代码一样赋值数组嘛···考虑给数组重新分配位置,使得最后算出来又是原来的顺序。
观察这幅图
最下面一层为我们数组应该保存的顺序。可以发现,他们的位置,刚好就是原下标在二进制下的翻转(开头为0,结尾为次数界n在二进制下的最高位的翻转)。
然后对于上面几层,我们每个组里面有多个值。
发现如果按照刚刚那个顺序存,每一层转移到下一层的时候,一个
设一行分为cnt组,即相同的
这个大概理解一下就好,反正代码很简洁明了。
代码
#include<cstdio>#include<algorithm>#include<cstring>#include<bitset>#include<cmath>#define fo(i,j,k) for(i=j;i<=k;i++)#define fd(i,j,k) for(i=j;i>=k;i--)typedef long long ll;typedef double db;using namespace std;const int N=400005;const db pi=acos(-1),eps=1e-5;struct vec{ db a,b; vec (db ax=0,db bx=0) {a=ax,b=bx;}};vec operator +(vec a,vec b){ return vec(a.a+b.a,a.b+b.b);}vec operator -(vec a,vec b){ return vec(a.a-b.a,a.b-b.b);}vec operator *(vec a,vec b){return vec(a.a*b.a-a.b*b.b,a.a*b.b+a.b*b.a);}int Log,m,len,i,n,ans[N];vec t[N],a[N],b[N];void DFT(vec *a,int n,int sig){ int i,j,k,half,siz; vec u,v,w; fo(i,0,n-1) { int p=0; for(int pos=0,tmp=i;pos<Log;pos++,tmp/=2) p=(p<<1)+(tmp&1); t[p]=a[i]; } for(int m=2; m<=n; m*=2) { int half = m/2; for(int i=0; i<half; i++) { vec w( cos(i*sig*pi/half), sin(i*sig*pi/half)); for(int j=i; j<n; j+=m) { k=j+half; u=t[j];v=w*t[k]; t[j]=u+v; t[k]=u-v; } } } fo(i,0,n-1) a[i]=t[i];}void FFT(vec *a,vec *b){ DFT(a,len,1); DFT(b,len,1); fo(i,0,len-1) a[i]=a[i]*b[i]; DFT(a,len,-1); fo(i,0,len-1) a[i].a/=(db)len;}int max2(int x){ int ret; for(ret=1;ret<x;ret<<=1); return ret<<1;}int ci(int x) {return (x==1)?0:ci(x/2)+1;}int main(){ freopen("taxi.in","r",stdin); scanf("%d %d",&n,&m); n++;m++; len=max2(max(n,m)); Log=ci(len); fo(i,0,n-1) scanf("%lf",&a[i].a); fo(i,0,m-1) scanf("%lf",&b[i].a); FFT(a,b); fo(i,0,n+m-1) ans[i]=round(a[i].a+eps); fo(i,0,n+m-2) printf("%d ",ans[i]);}//注意,由于是实数运算,所以给出系数在10^5以上时很容易出现误差
总结
中心思想:点值运算与插值运算
优化时间复杂度:选取n次单位复数根
难点:n次单位复数根的各种性质
重点:FFT的分治思想,DFT与逆DFT
要背的地方:FFT实现优化
- FFT—快速傅里叶变换学习小记
- 快速傅里叶变换 FFT 学习小记
- [FFT] 快速傅里叶变换学习笔记
- FFT,快速傅里叶变换学习笔记
- [学习笔记]快速傅里叶变换 FFT
- FFT(快速傅里叶变换)算法学习笔记
- 快速傅里叶变换(FFT)(ZZ)
- FFT快速傅里叶变换;
- 快速傅里叶变换(FFT)
- 关于FFT快速傅里叶变换
- FFT快速傅里叶变换
- FFT - 快速傅里叶变换
- 快速傅里叶变换(FFT)
- 快速傅里叶变换(FFT)
- GSL快速傅里叶变换FFT
- 快速傅里叶变换FFT
- 【数学】快速傅里叶变换(FFT)
- FFT 快速傅里叶变换 初探
- Android上手机软件调用外部地图软件
- LeetCode 367. Valid Perfect Square
- KMP
- 进程同步与互斥
- HTTP中application/x-www-form-urlencoded字符说明
- FFT—快速傅里叶变换学习小记
- android中使用对象池 ----- Pools
- RBtree详解之删除(含完整红黑树代码)
- 线段树
- C基础 对字符串数组的sizeof和strlen的区别
- Retrofit 注解 详解
- 页面主体高度不固定,随着高度的变化,固定在底部的元素始终不变化
- Java中的多态、抽象类、接口
- PowerShell中查看命令帮助