FFT快速傅里叶变换

FFT快速傅里叶变换

some 多项式变换

DFT:离散傅里叶变换 O(n2)计算多项式乘法
FFT:快速傅里叶变换 O(nlogn)计算多项式乘法
FNTT/NTT:快速傅里叶变换的优化版 优化常数及误差
FWT:快速沃尔什变换 利用类似FFT的东西解决一类卷积问题
MTT:毛爷爷的FFT 非常nb/任意模数
FMT 快速莫比乌斯变化

快速傅里叶变换

快速傅里叶变换(FFT) 算法的基本思想是分治。

DFT 来说,它分治地来求当 x=ωnk 的时候 f(x) 的值。

FFT 的分治思想体现在将多项式分为奇次项和偶次项处理。

对于一个 n1 次的多项式:

f(x)=a0+a1x+a2x2+...+an2xn2+an1xn1

按照次数的奇偶来分成两组,然后右边提出来一个 x

f(x)=(a0+a2x2+a4x4+...+an2xn2)+(a1x+a3x3+a5x5+...+an1xn1)=(a0+a2x2+a4x4+...+an2xn2)+x(a1+a3x2+a5x4+an1xn2)

分别用奇偶次次项数建立新的函数:

G(x)=a0+a2x+a4x2+...+an2xn21H(x)=a1+a3x+a5x2+...+an1xn21

那么原来的 f(x) 用新函数表示为:

f(x)=G(x2)+x×H(x2)

利用偶数次单位根的性质:ωni=ωni+n2

G(x2)H(x2) 是偶函数

我们知道在复平面上 ωniωni+n2G(x2)H(x2) 对应的值相同。

得到:

f(ωnk)=G((ωnk)2)+ωnk×H((ωnk)2)=G(ωn2k)+ωnk×H(ωn2k)=G(ωn2k)+ωnk×H(ωn2k)

和:

f(ωnk+n2)=G(ωn2k+n)+ωnk+n2×H(ωn2k+n)=G(ωn2k)ωnk×H(ωn2k)=G(ωn2k)ωnk×H(ωn2k)

因此我们求出了 G(ωn2k)H(ωn2k) 后,就可以同时求出 f(ωnk)f(ωnk+n2)

于是对 GH 分别递归 DFT 即可。

考虑到分治 DFT 能处理的多项式长度只能是 2m(mN),否则在分治的时候左右不一样长,右边就取不到系数了。

所以要在第一次 DFT 之前就把序列向上补成长度为 2m(mN)(高次系数补 0)、最高项次数为 2m1 的多项式。

位逆序置换

原序列的每个数用二进制表示,然后把二进制翻转对称一下,就是最终位置的下标。
可以在 O(nlogn)O(n) 的时间内求出每个数变换后的结果

蝶形运算优化

已知 G(ωn2k)H(ωn2k) 后,需要使用下面两个式子求出 f(ωnk)f(ωnk+n2)

f(ωnk)=G(ωn2k)+ωnk×H(ωn2k)f(ωnk+n2)=G(ωn2k)ωnk×H(ωn2k)

使用位逆序置换后,对于给定的 n, k

G(ωn2k) 的值存储在数组下标为 k 的位置,H(ωn2k) 的值存储在数组下标为 k+n2 的位置。

f(ωnk) 的值将存储在数组下标为 k 的位置,f(ωnk+n2) 的值将存储在数组下标为 k+n2 的位置。
因此可以直接在数组下标为 k

k+n2 的位置进行覆写,而不用开额外的数组保存值。
再详细说明一下如何借助蝶形运算完成所有段长度为 n2 的合并操作:

令段长度为 s=n2

同时枚举序列 {G(ωn2k)} 的左端点 lg=0,2s,4s,,N2s 和序列 {H(ωn2k)} 的左端点 lh=s,3s,5s,,Ns

合并两个段时,枚举 k=0,1,2,,s1,此时G(ωn2k) 存储在数组下标为 lg+k 的位置,H(ωn2k) 存储在数组下标为 lh+k 的位置;

使用蝶形运算求出 f(ωnk)f(ωnk+n2),然后直接在原位置覆写。

快速傅里叶逆变换

bi=ωni,则多项式 Ax=b0,b1,,bn1 处的点值表示法为 {A(b0),A(b1),,A(bn1)}

A(x) 的定义式做一下变换,可以将 A(bk) 表示为

A(bk)=i=0n1f(ωni)ωnik=i=0n1ωnikj=0n1aj(ωni)j=i=0n1j=0n1ajωni(jk)=j=0n1aji=0n1(ωnjk)i

S(ωna)=i=0n1(ωna)i

a=0(modn) 时,S(ωna)=n

a0(modn) 时,我们错位相减

S(ωna)=i=0n1(ωna)iωnaS(ωna)=i=1n(ωna)iS(ωna)=(ωna)n(ωna)0ωna1=0

也就是说

S(ωna)={n,a=00,a0

那么代回原式

A(bk)=j=0n1ajS(ωnjk)=akn

也就是说给定点 bi=ωni,则 A 的点值表示法为

{(b0,A(b0)),(b1,A(b1)),,(bn1,A(bn1))}={(b0,a0n),(b1,a1n),,(bn1,an1n)}

综上所述,我们取单位根为其倒数,对 {y0,y1,y2,,yn1} 跑一遍 FFT,然后除以 n 即可得到 f(x) 的系数表示。

递归写法

il void FFT(qwq *q,int lim,int k){
    if(lim==1) return;
    for(ri int i=0;i<lim;++i) o[i]=q[i];
    ri int m=lim>>1;
    for(ri int i=0;i<lim;i++){
        if(i&1) q[m+(i>>1)]=o[i];
        else q[i>>1]=o[i];
    }
    FFT(q,m,k),FFT(q+m,m,k);
    wn={cos(Pi*2/lim),k*sin(Pi*2/lim)},w={1,0};
    for(ri int i=0;i<m;++i,w=w*wn){
        x=q[i],y=w*q[i+m];
        q[i]=x+y,q[i+m]=x-y;
    } 
    return;
}
code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;

namespace Q{
    il int rd(){
        ri int x=0;ri bool f=0;ri char c=getchar();
        while(!isdigit(c)) f|=(c==45),c=getchar();
        while(isdigit(c)) x=x*10+(c^48),c=getchar();
        return f?-x:x;
    }
    il void wt(int x){
        if(x<0) x=-x,putchar(45);
        if(x>=10) wt(x/10);
        return putchar(x%10|48),void();
    }
} using namespace Q;

cs double Pi=3.141592653589793;
cs int N=2097152;

namespace FastFastTle{
    struct qwq{
        double r,i;
        qwq operator +(cs qwq o)cs{
            return (qwq){r+o.r,i+o.i};
        }
        qwq operator -(cs qwq o)cs{
            return (qwq){r-o.r,i-o.i};
        } 
        qwq operator *(cs qwq o)cs{
            return (qwq){r*o.r-i*o.i,o.r*i+r*o.i};
        }
    }wn,w,f[N],g[N],o[N],x,y; 
    il void FFT(qwq *q,int lim,int k){
        if(lim==1) return;
        for(ri int i=0;i<lim;++i) o[i]=q[i];
        ri int m=lim>>1;
        for(ri int i=0;i<lim;i++){
            if(i&1) q[m+(i>>1)]=o[i];
            else q[i>>1]=o[i];
        }
        FFT(q,m,k),FFT(q+m,m,k);
        wn={cos(Pi*2/lim),k*sin(Pi*2/lim)},w={1,0};
        for(ri int i=0;i<m;++i,w=w*wn){
            x=q[i],y=w*q[i+m];
            q[i]=x+y,q[i+m]=x-y;
        } 
        return;
    }
} using namespace FastFastTle;

signed main(){
    int n=rd(),m=rd(),lim=1;
    while(lim<=n+m) lim<<=1;
    for(ri int i=0;i<=n;++i) f[i].r=rd();
    for(ri int i=0;i<=m;++i) g[i].r=rd();
    FFT(f,lim,1),FFT(g,lim,1);
    for(ri int i=0;i<lim;++i) f[i]=f[i]*g[i];
    FFT(f,lim,-1);
    for(ri int i=0,s;i<=n+m;++i){
        wt(f[i].r/lim+0.5),putchar(32);
    }
    return 0;
}

迭代实现

il void FFT(qwq q[],int lim,int k){
    for(ri int i=0;i<lim;++i){
        if(i<r[i]) swap(q[i],q[r[i]]);
    }
    for(ri int mid=1;mid<lim;mid<<=1){
        wn={cos(Pi/mid),k*sin(Pi/mid)};
        for(ri int R=mid<<1,j=0;j<lim;j+=R){
            w={1,0};
            for(ri int t=0;t<mid;++t,w=w*wn){
                x=q[j+t],y=w*q[j+mid+t];
                q[j+t]=x+y,q[j+mid+t]=x-y;
            }
        }
    } 
    return;
}
code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;

namespace Q{
    il int rd(){
        ri int x=0;ri bool f=0;ri char c=getchar();
        while(!isdigit(c)) f|=(c==45),c=getchar();
        while(isdigit(c)) x=x*10+(c^48),c=getchar();
        return f?-x:x;
    }
    il void wt(int x){
        if(x<0) x=-x,putchar(45);
        if(x>=10) wt(x/10);
        return putchar(x%10|48),void();
    }
} using namespace Q;

cs double Pi=3.141592653589793;
cs int N=2097152;

namespace FastFastTle{
    struct qwq{
        double r,i;
        qwq operator +(cs qwq o)cs{
            return (qwq){r+o.r,i+o.i};
        }
        qwq operator -(cs qwq o)cs{
            return (qwq){r-o.r,i-o.i};
        } 
        qwq operator *(cs qwq o)cs{
            return (qwq){r*o.r-i*o.i,o.r*i+r*o.i};
        }
    }wn,w,f[N],g[N],x,y;
    int l,r[N];
    il void FFT(qwq q[],int lim,int k){
        for(ri int i=0;i<lim;++i){
            if(i<r[i]) swap(q[i],q[r[i]]);
        }
        for(ri int mid=1;mid<lim;mid<<=1){
            wn={cos(Pi/mid),k*sin(Pi/mid)};
            for(ri int R=mid<<1,j=0;j<lim;j+=R){
                w={1,0};
                for(ri int t=0;t<mid;++t,w=w*wn){
                    x=q[j+t],y=w*q[j+mid+t];
                    q[j+t]=x+y,q[j+mid+t]=x-y;
                }
            }
        } 
        return;
    }
} using namespace FastFastTle;

signed main(){
    int n=rd(),m=rd(),lim=1;
    while(lim<=n+m) lim<<=1,l++;
    for(ri int i=0;i<=n;++i) f[i].r=rd();
    for(ri int i=0;i<=m;++i) g[i].r=rd();
    for(ri int i=0;i<lim;++i){
        r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    }
    FFT(f,lim,1),FFT(g,lim,1);
    for(ri int i=0;i<lim;++i) f[i]=f[i]*g[i];
    FFT(f,lim,-1);
    for(ri int i=0,s;i<=n+m;++i){
        wt(f[i].r/lim+0.5),putchar(32);
    }
    return 0;
}

edit

posted @   雨夜风月  阅读(40)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示