FFT快速傅里叶

快速傅里叶

(这个博客主要帮助自己记着FFT这个算法,并不是讲解用的QAQ)

定义:

现在有两个多项式:

\(f(x)=a_1+a_2x+a_3x^2+...+a_nx^{n-1}\)

\(g(x)=b_1+b_2x+g_3x^2+...+g_mx^{m-1}\)

加入我们计算 \(f(x)*g(x)\) 的表达式时,暴力算法的复杂度为 \(O(n^2)\) ,我们这时候就需要引入快速傅里叶算法来解决这个问题。

思想:

1.初始想法

枚举出 \(n+1\) 个点,计算出其分别在 \(f(x),g(x)\) 上对应的坐标,利用拉格朗日插值去求解。

2.减少时间

虽然这样看上去简单了,但是时间复杂度还是 \(O(n^2)\) ,因此我们可以观察原来每个函数的性质:如果这个函数是偶函数,那么枚举 \(n+1\) 个点,根据偶函数性质我们就可以得到 \(2n+2\) 个点的坐标,这样就可以减少枚举的次数了

3.转化多项式

例如

\(x^5+x^4+3x^3+2x^2+5x\)

可以转化成:

\((x^4+2x^2)+x(x^4+3x^2+5)\)

这样,每一个括号内就变成了偶函数。

将前后两个式子分别定义为 \(a,b\) ,那么 \(x>0\) 时,\(f(x)=a+b\),反之,则是 \(f(x)=a-b\)

4.利用复数进行枚举

根据就当是我已经会了的单位圆的知识可以得到:\(n\) 等分的单位元可以利用复数得到不同的值,从而很好的减少了枚举的时间复杂度。

同时,单位元还有个性质,就是前后可以同时算(QAQ懒得证明了)。

后面:
\(\large{F(ω^{k+n/2}_n)=FL(ω^{k}_{n/2})-ω^k_nFR(ω^{k}_{n/2}})\)

前面:
\(\large{F(ω^{k}_n)=FL(ω^{k}_{n/2})+ω^k_nFR(ω^{k}_{n/2}})\)

前后除了正负号其他都一样。

这样进行分治就可以啦啦啦啦啦啦。

5.保证limit是2的整数次幂

为什么要这样呢?我们可以想一想,如果分的时候不是2的整数次幂,就会分乱,因此要定义一个 \(limit\) 进行左右划分。

代码:

while(limit<=n+m) limit<<=1,l++;

6.复数计算:

利用 \(STL\) 中的 $complex $ 代码,重定义三维运算。

complex operator + (complex a,complex b){
    return complex(a.x+b.x,a.y+b.y);
}
complex operator - (complex a,complex b){
    return complex(a.x-b.x,a.y-b.y);
}
complex operator * (complex a,complex b){
    return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);
}

7.反演:

就是传的参数不一样。

8.蝴蝶优化:

我们在进行多项式拆解的时候还是需要去一个一个算,那么怎么快速拆解呢?
根据网上的理论:

一个数的二进制表达反过来就是其应该在的位置的二进制表达

例如5的二进制是100,其应该在的位置就是001,也就是1。

就有接下来一串代码:

fft中:
for(int i=0;i<limit;i++)//每对数只反转一次
        if(i<r[i]) swap(A[i],A[r[i]]);

main中:
for(int i=0;i<limit;i++){
        r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    }

模板:

#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const double PI=acos(-1.0);
const int N=2e6+5;
int n,m;
int limit,l,r[N];
struct complex{
    double x,y;
    complex(double xx=0,double yy=0){x=xx,y=yy;}
}a[N],b[N];
complex operator +(complex a,complex b){
    return complex(a.x+b.x,a.y+b.y);
}
complex operator -(complex a,complex b){
    return complex(a.x-b.x,a.y-b.y);
}
complex operator *(complex a,complex b){
    return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y+b.x);
}
void fft(complex *A,int type){
    for(int i=0;i<limit;i++)//每对数只反转一次
        if(i<r[i]) swap(A[i],A[r[i]]);
    for(int mid=1;mid<limit;mid<<=1){//等待合并区间的中点
        complex Wn( cos(Pi/mid),type*sin(Pi/mid));//单位根

        for(int R=mid<<1,j=0;j<limit;j+=R){
            complex w(1,0);
            for(int k=0;k<mid;k++,w=w*Wn){//枚举左半部分
                complex x=A[j+k],y=w*A[j+mid+k];
                A[j+k]=x+y;
                A[j+mid+k]=x-y;
            }
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i].x;
    for(int i=1;i<=m;i++) cin>>b[i].x;
    while(limit<=n+m) limit<<=1,l++;
    for(int i=0;i<limit;i++){
        r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    }
    fft(a,1),fft(b,1);
    for(int i=0;i<limit;i++) a[i]=a[i]*b[i];
    fft(a,-1);
    for(int i=0;i<=n+m;i++)
    .......//此时求出来的就是多项式每一位的系数
    return 0;
}

注意事项:

1.不能用万能头......

2.暂时没发现QAQ

上一篇:数据库分库分表后”跨库分页“查询方案


下一篇:函数小入门