对FFT的理解&&FFT模板

FFT(Fast Fourier Transform)

与其有关的有

DFT(Discrete Fourier Transform)

NTT(Number Theoretic Transforms)

类似的有

FWT(Fast Walsh-Hadamard Transform)

与FFT有关的应用

多项式乘法,卷积等等。


FFT算法讲解

一些你要知道的东东:

多项式相关

  • 多项式:多个单项式相加得到的整式
  • 多项式的次数界:其最高次项的次数加1,次数界为n的多项式最高次为 xn1
  • 多项式的表示方法为 A(x)=n1i=0aixi
  • 多项式的点值运算:将多个不同值代入多项式的x,得出多个 (xi,A(xi)) 为该多项式的点值运算,一般用n的点(n为该多项式的次数界)
  • 多项式的差值运算:已知n个点得出过这些点次数界n的多项式

复数相关

  • 虚数单位i:令 i=1 ,满足 i2=1i3=1i=i21i=1+i 等诸多有趣性质
  • 复数:形如 a+bi 的数成为复数,其中a为实部,b为虚部
  • 复数直角坐标系:和平面直角坐标系类似,只是用x轴y轴表示实部虚部
  • 复数的模长: |z:a+bi|=a2+b2 即为该复数在复数直角坐标系中与原点的距离。
  • 复数的辐角:复数直角坐标系中该复数与x轴的夹角
    complex
  • 复数相乘,模长相乘,辐角相加(用和角公式可以证明)

    令复数 z1:a+bi 辐角为 θ1 z2:x+yi 辐角为 θ2 z3=z1z2=axby+(ay+bx)i
    |z1|=l1,|z2|=l2,z1=z1/l1,z2=z2/l2 z1:a+bi,z2:x+yi
    |z1|=|z2|=1a=cos(θ1),b=sin(θ1),x=cos(θ2),y=sin(θ2)
    z3=z1z2=l1l2z1z2=l1l2(axby+(ay+bx)i)=l1l2(cos(θ1)cos(θ2)sin(θ1)sin(θ2)+(cos(θ1)sin(θ2)+sin(θ1)cos(θ2))i)
    z3=l1l2(cos(θ1+θ2)+sin(θ1+θ2)i)
    z3:cos(θ1+θ2)+sin(θ1+θ2)iz3=l1l2z3
    易知 |z3|=1|z3|=|z1||z2|

  • 方程 xn=1 的n次单位复数根为 eiθ=cos(θ)+sin(θ)i (为什么,后面证)其中 θ=2π/n ,第k个根为 Wkn=eikπn ,令 Wn=W1n

  • Wkn=(Wn)k Wk+n2n=Wkn Wan+kn=Wkn Wdkdn=Wkn (消去引理), (Wkn)2=W2kn=Wkn2 (折半引理)

——FFT干什么的?
——它是用来处理点值运算和插值运算的,这样它就可以实现多项式乘法了。

因为 A(x)B(x)=C(x) 所以只要知道 A(x)B(x) 的2n个点值,相乘就可以知道 C(x) 的2n个点值,再通过差值运算求出 C(x) 的表达式

点值运算

Yk=A(Wkn)=l=0n1aleikπn

点值的取值呢?如果直接带入的话是 O(n2)
想到n次单位复数根与折半引理,是否可以利用分治呢?
下面给出算法:

A(x)=n1i=0=a0+a1x1+a2x2++an1xn1
A0(x)=a0+a2x1+a4x2++an2xn2
A1(x)=a1+a2x1+a3x2++an1xn2
易得 A(x)=A0(x2)+xA1(x2)
x=W0n1n
x<n2 时,令 2n=n

A(Wkn)=A0((Wkn)2)+WknA1((Wkn)2)=A0(W2kn)+WknA1(W2kn)=A0(Wkn)+WknA1(Wkn)

A(Wk+nn)=A0((Wk+nn)2)+Wk+nnA1((Wk+nn)2)=A0(W2k+nn)+Wk+nnA1(W2k+nn)=A0(Wk+nn)WknA1(Wk+nn)=A0(Wkn)WknA1(Wkn)

发现两条式子仅仅只是中间的加减号区别,所以只用枚举 0n2 然后递归下去。
流程大致如下:

void FFT(&A)
    A0={a[0],a[2],a[4],...,a[n-2]}
    A1={a[1],a[3],a[5],...,a[n-1]}
    FFT(a0)FFT(a1)
    for k(0->n/2-1)
        A[k]=A0[k]+W[n/2][k]*A1[k]
        A[k]=A0[k]-W[n/2][k]*A1[k]

差值运算

推式子可得

ak=1nl=0n1yleikπn

也就是将点值运算的 ay 反过来, e 的幂取负,再除以n即可。

实现

如果每一次都递归一个数组的话,空间肯定会爆爆炸
观察它的转移方法:
FFT
发现x的排序方式是按照二进制反串的大小排的
给出一个高级式子:

for(int i=0,p=0;i<n;i++){
    if(i<p)swap(x[i],x[p]);
    for(int j=n>>1;(p^=j)<j;j>>=1);
}

然后再枚举哪一个位置哪一块转移即可。

void dft(z *a,db sig){
    for(int i=0;i<n;++i)y[i]=a[i];
    for(int i=0,p=0;i<n;i++){
        if(i<p)swap(y[i],y[p]);
        for(int j=n>>1;(p^=j)<j;j>>=1);
    }
    for(int m=2;m<=n;m<<=1){
        int hf=m>>1;
        for(int i=0;i<hf;i++){
            db aug=(db)i/(db)hf*M_PI*sig;
            z w=(z){cos(aug),sin(aug)};
            for(int j=i;j<=n;j+=m){
                int k=j+hf;
                z u=y[j],v=y[k]*w;
                y[j]=u+v;y[k]=u-v;
            }
        }
    }
    for(int i=0;i<n;i++)a[i]=y[i];
}

呃呃插一句对 eiθ=cos(θ)+sin(θ)i 的证明:
这是个不严谨的严谨证明,最严谨的严谨证明要用到泰勒公式,我不会。。。
欧拉定理1
欧拉定理2

猜你喜欢

转载自blog.csdn.net/white_elephant/article/details/79284208
FFT