FFT简介(洛谷P1919/3803、BZOJ2179)

学习自FFT详解

很久前就想学,然而一直不能理解,这两天稍微懂了一些。

含义及用途

FFT(Fast Fourier Transformation)是离散傅氏变换(DFT)的快速算法。即为快速傅氏变换。它是根据离散傅氏变换的奇、偶、虚、实等特性,对离散傅立叶变换的算法进行改进获得的,把多项式乘法的复杂度从 O ( n 2 ) 降到了 O ( n log n ) (然而常数很大)。

过程

前置知识

多项式的两种表示方法

设多项式 f ( x ) = i = 0 n a i x i

系数表示法: f ( x ) = { a 0 , a 1 , a 2 , a n }

点值表示法:把多项式看成函数, { ( x 0 , y 0 ) , ( x 1 , y 1 ) , ( x 2 , y 2 ) , ( x n , y n ) } 为函数上的点,则 f ( x ) = { ( x 0 , y 0 ) , ( x 1 , y 1 ) , ( x 2 , y 2 ) , ( x 0 , y 0 ) }

单位复根

ω n 。其中 ω n 0 = ω n n = 1 , ω n k = ( cos 2 k π n , sin 2 k π n )

则有 ω 2 n 2 k = ω n k , ω n k + n 2 = ω n k

DFT

f ( x ) 的项按照次数奇偶分开,则有 f ( x ) = a 0 + a 2 x 2 + + a n 2 x n 2 + x ( a 1 + a 3 x 2 + + a n 1 x n 2 )

f 1 ( x ) = a 0 + a 2 x + + a n 2 x n 2 1 , f 2 ( x ) = a 1 + a 3 x + + a n 1 x n 2 1

f ( x ) = f 1 ( x 2 ) + x f 2 ( x 2 )

这样可以一直递归下去。

然而会发现左右两半长度不相同,会没法搞,于是我们把位数补到 2 k ,然后把 2 k 的单位复根代入即可。

然而递归很慢,我们可以尝试找找规律。

假设 f ( x ) 的最高项次数为 8

我们来模拟一下:

(1) a 0 , a 1 , a 2 , a 3 , a 4 , a 5 , a 6 , a 7 (2) ( a 0 , a 2 , a 4 , a 6 ) , ( a 1 , a 3 , a 5 , a 7 ) (3) ( a 0 , a 4 ) , ( a 2 , a 6 ) , ( a 1 , a 5 ) , ( a 3 , a 7 ) (4) ( a 0 ) , ( a 4 ) , ( a 2 ) , ( a 6 ) , ( a 1 ) , ( a 5 ) , ( a 3 ) , ( a 7 )

把编号转化成二进制,前后比对:

( 000 ) , ( 001 ) , ( 010 ) , ( 011 ) , ( 100 ) , ( 101 ) , ( 110 ) , ( 111 )

( 000 ) , ( 100 ) , ( 010 ) , ( 110 ) , ( 001 ) , ( 101 ) , ( 011 ) , ( 111 )

然后通过看别人Blog观察可以发现(当然可以证明)最终编号就是原来编号在二进制下的翻转。

于是我们可以省去递归的操作,直接从底回溯即可(实际上用“蝴蝶操作”可以直接迭代,这里仅作代码注释)。

IDFT

把单位复根取倒数,做一遍DFT并把结果除以 n 就可以把点值表示转化为系数表示法。

因为博主太菜证不来这里不做证明,实现见代码。

模板

洛谷P3803

#include<cmath> 
#include<cctype>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1<<21
#define F inline
#define fr first
#define sc second
#define MP make_pair 
using namespace std;
typedef long double DB;
typedef pair<DB,DB> P;
const DB pi=acos(-1);
int n,m,l,R[N],c[N];
P a[N],b[N];
F char readc(){
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    return l==r?EOF:*l++;
}
F int _read(){
    int x=0; char ch=readc();
    while (!isdigit(ch)) ch=readc();
    while (isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=readc();
    return x;
}
F void writec(int x){ if (x>9) writec(x/10); putchar(x%10+48); }
F void _write(int x){ writec(x),putchar(' '); }
//以下三个运算为在复平面内的运算
F P operator + (P a,P b){ return MP(a.fr+b.fr,a.sc+b.sc); }
F P operator - (P a,P b){ return MP(a.fr-b.fr,a.sc-b.sc); }
F P operator * (P a,P b){ return MP(a.fr*b.fr-a.sc*b.sc,a.fr*b.sc+a.sc*b.fr); }
F void FFT(P *a,int n,int f){
    for (int i=0;i<n;i++) if (i<R[i]) swap(a[i],a[R[i]]);
    //把原来的转化为最终的多项式,这里大于小于都无所谓,只要不换回来就行
    for (int k=1;k<n;k<<=1){//蝴蝶操作,k表示当前合并的大小
        P w=MP(1,0),wn=MP(cos(pi/k),sin(f*pi/k)),x,y;
        //wn为单位复根,w为sqrt(-1),x为f1,y为f2
        for (int i=0;i<n;i+=(k<<1),w=MP(1,0))
            for (int j=0;j<k;j++,w=w*wn)
                x=a[i+j],y=w*a[i+j+k],a[i+j]=x+y,a[i+j+k]=x-y;
    }
}
int main(){
    n=_read(),m=_read();
    for (int i=0;i<=n;i++) a[i]=MP(_read(),0);
    for (int i=0;i<=m;i++) b[i]=MP(_read(),0);
    for (m+=n,n=1;n<=m;n<<=1) l++;
    for (int i=0;i<=n;i++)
        R[i]=(R[i>>1]>>1)|((i&1)<<(l-1));//翻转
    FFT(a,n,1),FFT(b,n,1);
    for (int i=0;i<n;i++) a[i]=a[i]*b[i];
    FFT(a,n,-1);
    for (int i=0;i<=m;i++) _write(a[i].fr/n+0.5);
    return 0;
}

经典应用:高精度乘法。

把每个数看作多项式每一项之前的系数即可。

洛谷P1919
BZOJ2179

代码:

#include<cmath>
#include<cctype>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1<<18
#define F inline
using namespace std;
typedef long double DB;
const DB pi=acos(-1);
struct P{ DB x,y; }a[N],b[N];
int n,l,m,r[N],c[N],ans[N];
char bf[N];
F char readc(){
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    return l==r?EOF:*l++;
}
F int _read(){
    int x=0; char ch=readc();
    while (!isdigit(ch)) ch=readc();
    return ch-48;
}
F P operator + (P a,P b){ return (P){a.x+b.x,a.y+b.y}; }
F P operator - (P a,P b){ return (P){a.x-b.x,a.y-b.y}; }
F P operator * (P a,P b){ return (P){a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x}; }
F void FFT(P *a,int f){
    for (int i=0;i<n;i++) if (i<r[i]) swap(a[i],a[r[i]]);
    for (int k=1;k<n;k<<=1){
        P w={1,0},wn={cos(pi/k),sin(f*pi/k)},x,y;
        for (int i=0;i<n;i+=(k<<1),w=(P){1,0})
            for (int j=0;j<k;j++,w=w*wn)
                x=a[i+j],y=w*a[i+j+k],a[i+j]=x+y,a[i+j+k]=x-y;
    }
}
F void _write(){
    for (int i=0;i<=m;i++) bf[i]=(char)ans[m-i]+48;
    puts(bf);
}
int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        a[n-i]=(P){_read(),0};
    for (int i=1;i<=n;i++) b[n-i]=(P){_read(),0};
    for (m=n<<1|1,n=1;n<m-1;n<<=1) l++;
    for (int i=0;i<=n;i++)
        r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    FFT(a,1),FFT(b,1);
    for (int i=0;i<n;i++) a[i]=a[i]*b[i];
    FFT(a,-1);
    for (int i=0;i<=n;i++){
        ans[i]+=a[i].x/n+0.5;
        ans[i+1]+=ans[i]/10,ans[i]%=10;
    }
    while (!ans[m]&&m) m--;
    return _write(),0;
}

猜你喜欢

转载自blog.csdn.net/a1799342217/article/details/80962290