快速傅里叶变换FFT学习笔记
点值表示法
我们正常表示一个多项式的方式,形如
我们知道,
于是,我们任意地取
复数
一种表示坐标的方法,对于坐标
C++中有复数的模板,complex
,可以直接作为变量类型使用。
运算规则,自然不用多说了,也就是直接拿式子算即可。
如果你不会,可以看看百度百科。
我们将点至原点的距离称为模长,将其与原点相连之后与
我的表述自然不够专业,希望可以表述出这个意思吧。
复数的乘法可以理解为,模长相乘,辐角相加。
Tips:
此部分的证明来自 cjx 犇犇。
为啥是这样呢?证明如下:
那么这个点就是
那么我们应该可以看出来这个模长相乘了。
接下来是辐角相加,我们设原来两个辐角为
我们知道分母是乘积的模长,分子是横坐标,所以这个式子恰好就是乘积辐角对应的
那么显然,辐角的值就是
把圆均分
如图是坐标轴上一个以原点为圆心的半径为
我们定义
那么,不难发现,
这是为什么呢?我们考虑它的辐角,由于其平分了一整个圆,所以其辐角为
简单推推式子不难发现
- 性质1:
,根据定义可证。 - 性质2:
,两点对称。
这个东西有什么用呢?
离散傅里叶变换
离散傅里叶变换(Discrete Fourier Transform,简称DFT)的思想是利用
对于一个多项式
于是我们得到了
这被称为
傅里叶逆变换
我们再将其作为一个多项式
对于这个多项式
易得:
关于后面那个等比数列,若
因此:
所以我们可以求出原来的多项式了。
有几点需要注意:
- 我们提取
只提取实部,因为虚部是虚数,无法经过我们的转换后变成实数。 - 数字有一定误差(毕竟你使用了三角函数等东西,小数是会有误差的),所以要四舍五入。
for(int i=0;i<=len-1;i++) { ans[i]+=floor(a[i].real()/len+0.5); }
快速傅里叶变换
快速傅里叶变换(Fast Fourier Transform,简称FFT),是在 DFT的基础上我们发现时间复杂度依然需要
我们可以使用分治的思想,使得时间复杂度降至
对于
同理:
不错!利用这两个式子,我们可以在
不过注意这里一直有一个除二操作,为了方便,我们需要把多项式补成一个次数为
可以写一个递归来求解。
注意这个取倒可以利用共轭复数,对于
由于此处
void FFT(cp *a,LL n,bool inv)//inv 表示omega是否取倒 { if(n==1)return; static cp buf[N]; LL m=n/2; for(int i=0;i<=m-1;i++)buf[i]=a[2*i],buf[i+m]=a[2*i+1];//奇偶分开 for(int i=0;i<=n-1;i++)a[i]=buf[i]; FFT(a,m,inv),FFT(a+m,m,inv); for(int i=0;i<=m-1;i++) { cp x=omega(n,i); if(inv)x=conj(x);//conj(x)求解共轭复数 buf[i]=a[i]+x*a[i+m],buf[i+m]=a[i]-x*a[i+m]; } for(int i=0;i<=n-1;i++)a[i]=buf[i]; }
利用FFT求解多项式的乘积
这个还是十分简单的,直接把两个多项式转化为两个长度相同的次数为
求解出其傅里叶变换形式之后,对于每个点对应的复数,相乘即可。
为什么呢?
这里有一个误区大家要注意,
首先我们知道当前的
因此多项式相乘以后,我们希望
给一个简单的实现:
#include<bits/stdc++.h> #define LL int #define cp complex<double> using namespace std; const double PI=acos(-1.0000); const int N=5e6+5; cp omega(LL n, LL k) { return cp(cos(2*PI*k/n),sin(2*PI*k/n)); } LL n,x,len,ans[N]; cp a[N],b[N]; //上文的 FFT 实现省去 int main() { scanf("%d",&n); len=1; while(len<2*n)len*=2; for(int i=n-1;i>=0;i--)scanf("%1d",&x),a[i].real(x); for(int i=n-1;i>=0;i--)scanf("%1d",&x),b[i].real(x); FFT(a,len,0),FFT(b,len,0); for(int i=0;i<=len-1;i++)a[i]*=b[i]; FFT(a,len,1); for(int i=0;i<=len-1;i++)//进位 { ans[i]+=floor(a[i].real()/len+0.5); ans[i+1]+=ans[i]/10; ans[i]%=10; } int i=len; for(i;i>=0&&ans[i]==0;i--);//前导零 if(i==-1)len=0; for(;i>=0;i--)printf("%d",ans[i]); }
非递归FFT
这里有一个优化,我们发现每次递归有一个把
我们不妨想一下,对于一个数
我们先将每个
void FFT(cp *a,bool inv) { LL lim=0; while((1<<lim)<len)lim++; for(int i=0;i<=len-1;i++) { LL t=0; for(int j=0;j<lim;j++) if((i>>j)&1)t|=(1<<(lim-j-1));//处理其翻转后的值 if(i<t)swap(a[i],a[t]); } static cp buf[N]; for(int l=2;l<=len;l*=2) { LL m=l/2; for(LL j=0;j<=len-1;j+=l) { for(LL i=0;i<=m-1;i++) { cp x=omega(l,i+j); if(inv)x=conj(x); buf[i+j]=a[i+j]+x*a[i+j+m]; buf[i+j+m]=a[i+j]-x*a[i+j+m]; } } for(int j=0;j<=len-1;j++)a[j]=buf[j]; } }
蝴蝶操作
这个东西其实就是想了个办法使得把工具人数组 buf
除掉了。
调调顺序即可。
void FFT(cp *a,bool inv) { LL lim=0; while((1<<lim)<len)lim++; for(int i=0;i<=len-1;i++) { LL t=0; for(int j=0;j<lim;j++) if((i>>j)&1)t|=(1<<(lim-j-1)); if(i<t)swap(a[i],a[t]); } for(int l=2;l<=len;l*=2) { LL m=l/2; for(LL j=0;j<=len-1;j+=l) { for(LL i=0;i<=m-1;i++) { cp x=omega(l,i+j); if(inv)x=conj(x); x*=a[i+j+m]; a[i+j+m]=a[i+j]-x; a[i+j]=a[i+j]+x; } } } }
一些小优化
对于
然后
最后代码就长这样了:
#include<bits/stdc++.h> #define LL int #define cp complex<double> using namespace std; const double PI=acos(-1.0000); const int N=5e6+5; cp omega(LL n, LL k) { return cp(cos(2*PI*k/n),sin(2*PI*k/n)); } LL n,len,lim,x,ans[N],r[N]; cp a[N],b[N]; void FFT(cp *a,bool inv) { for(int i=0;i<=len-1;i++) { LL t=r[i]; if(i<t)swap(a[i],a[t]); } for(int l=2;l<=len;l*=2) { LL m=l/2; cp omg=omega(l,1); for(LL j=0;j<=len-1;j+=l) { cp x(1,0); for(LL i=0;i<=m-1;i++) { cp t=x; if(inv)t=conj(t); t*=a[i+j+m]; a[i+j+m]=a[i+j]-t,a[i+j]=a[i+j]+t; x*=omg; } } } } int main() { scanf("%d",&n); len=1; while(len<2*n)len*=2,lim++; for(int i=0;i<=len-1;i++) { LL t=0; for(int j=0;j<lim;j++)if((i>>j)&1)t|=(1<<(lim-j-1)); r[i]=t; } for(int i=n-1;i>=0;i--)scanf("%1d",&x),a[i].real(x); for(int i=n-1;i>=0;i--)scanf("%1d",&x),b[i].real(x); FFT(a,0),FFT(b,0); for(int i=0;i<=len-1;i++)a[i]*=b[i]; FFT(a,1); for(int i=0;i<=len-1;i++) { ans[i]+=floor(a[i].real()/len+0.5); ans[i+1]+=ans[i]/10; ans[i]%=10; } int i=len; for(i;i>=0&&ans[i]==0;i--); if(i==-1)len=0; for(;i>=0;i--)printf("%d",ans[i]); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步