最近重新学了下卷积,简单总结一下,不涉及细节内容:

 

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;
}
FFT

相关文章: