最近重新学了下卷积,简单总结一下,不涉及细节内容:
1、FFT
朴素求法:$Coefficient-O(n^2)-CoefficientResult$
FFT:$Coefficient-O(nlogn)-Dot-O(n)-DotResult-O(nlogn)-CoefficientResult$
其中系数到点值的转化称为$DFT(离散傅里叶变换)$,而点值到系数的转为称为$IDFT(傅里叶逆变换)$
原本朴素的直接带入$n$个值的$DFT$和直接使用拉格朗日插值公式的$IDFT$的复杂度仍为$O(n^2)$
但$FFT$通过带入特定的值:单位根,使得两者都能迭代/分治得解决,将复杂度降到了$O(nlogn)$
优化的技巧和注意事项:
1、预处理$w[i]$
2、求出最终数组从后往前迭代省去递归常数
3、数组长度要先扩成2的倍数用于分治
模板:
#include <bits/stdc++.h> using namespace std; #define X first #define Y second #define pb push_back typedef double db; typedef long long ll; typedef pair<int,int> P; const int MAXN=3e6+10; struct Complex { db x,y; Complex(db a=0,db b=0){x=a;y=b;} Complex operator + (const Complex& rhs) {return Complex(x+rhs.x,y+rhs.y);} Complex operator - (const Complex& rhs) {return Complex(x-rhs.x,y-rhs.y);} Complex operator * (const Complex& rhs) {return Complex(x*rhs.x-y*rhs.y,x*rhs.y+y*rhs.x);} }a[MAXN],b[MAXN]; int n,m,lmt=1,dgt,par[MAXN]; void FFT(Complex *a,int flag) { for(int i=0;i<lmt;i++) if(i<par[i]) swap(a[i],a[par[i]]); for(int len=1;len<lmt;len<<=1) { Complex unit(cos(M_PI/len),flag*sin(M_PI/len)); for(int st=0;st<lmt;st+=(len<<1)) { Complex w(1,0); for(int k=st;k<st+len;k++,w=w*unit) { Complex A=a[k],B=w*a[k+len]; a[k]=A+B;a[k+len]=A-B; } } } if(flag==-1) for(int i=0;i<=n+m;i++) a[i].x=floor(a[i].x/lmt+0.5); } int main() { scanf("%d%d",&n,&m); for(int i=0;i<=n;i++) scanf("%lf",&a[i].x); for(int i=0;i<=m;i++) scanf("%lf",&b[i].x); while(lmt<=n+m) lmt<<=1,dgt++; for(int i=0;i<lmt;i++) par[i]=(par[i>>1]>>1)|((i&1)<<(dgt-1)); FFT(a,1);FFT(b,1); for(int i=0;i<lmt;i++) a[i]=a[i]*b[i]; FFT(a,-1); for(int i=0;i<=n+m;i++) printf("%d ",(int)a[i].x); return 0; }