FFT学习记录(雾)

前置知识:

1.点值表示

A ( x ) A(x) A(x)是一个 ( n − 1 ) (n - 1) (n1)次多项式,那么把 ( n ) (n) (n)个不同的 ( x ) (x) (x)代入,会得到 ( n ) (n) (n) ( y ) (y) (y) ( n ) (n) (n) ( x , y ) (x, y) (x,y)唯一确定了该多项式,即只有一个多项式能同时满足“代入这些 ( x ) (x) (x),得到的分别是这些 ( y ) (y) (y)”。

由多项式可以求出其点值表示,而由点值表示也可以求出多项式。

2.复数知识

复数相乘的规则:模长相乘,幅角相加。

3.单位根的性质

性质一: ω 2 n 2 k = ω n k \omega_{2n}^{2k} = \omega_{n}^{k} ω2n2k=ωnk

证明:它们对应的点/向量是相同的。

性质二: ω n k + n 2 = − ω n k \omega_{n}^{k+\frac{n}{2}}=-\omega_{n}^{k} ωnk+2n=ωnk

证明:它们对应的点是关于原点对称的(对应的向量是等大反向的)。

1.什么是FFT

快速傅里叶变换(FFT)是一种能在 O ( n log ⁡ n ) O(n\log n) O(nlogn)的时间内将一个多项式转换成它的点值表示的算法

2.为什么要使用FFT

FFT可以用来加速多项式乘法

假设有两个 n − 1 n-1 n1次的多项式 A ( x ) A(x) A(x) B ( x ) B(x) B(x),我们的目标是把它们乘起来。

普通的多项式乘法是 O ( n 2 ) O(n^2) O(n2)的。

因为我们需要枚举 A ( x ) A(x) A(x)中的每一项,分别与 B ( x ) B(x) B(x)中的每一项相乘得到多项式 C ( x ) C(x) C(x)

但有趣的是,两个用点值表示的多项式相乘,复杂度是 O ( n ) O(n) O(n)的。

即:

C ( x i ) = A ( x i ) × B ( x i ) C(x_i)=A(x_i)\times B(x_i) C(xi)=A(xi)×B(xi),所以 O ( n ) O(n) O(n)枚举 x i x_i xi就行了

如果把两个多项式转换成点值表示,再相乘,在把新的点值表示转换成多项式不就可以 O ( n ) O(n) O(n)解决多项式乘法了?

但是,把多项式转换成点值表示的朴素算法是 O ( n 2 ) O(n^2) O(n2)

所以大整数乘法的复杂度瓶颈就在于多项式转换成点值表示这一步,只要完成这步,就能 O ( n ) O(n) O(n)求答案了。

这时傅里叶就跑出来了…

3.离散傅里叶变换(快速傅里叶变换的朴素版)

傅里叶发明了一种办法:规定点值表示中的 n n n x x x n n n个模长为 1 1 1复数

C++的STL提供了复数模板

定义:complex<double> x;
运算:直接使用加减乘除。

傅里叶要用到的 n n n个复数,不是随机找的,而是——把单位圆(圆心为原点、 1 1 1为半径的圆) n n n等分,取这 n n n个点(或点表示的向量)所表示的虚数,即分别以这 n n n个点的横坐标为实部、纵坐标为虚部,所构成的虚数。

luKaG9.png

从点 ( 1 , 0 ) (1,0) (1,0)开始(该点显然是要去的点之一),逆时针将这 n n n个点从 0 0 0开始编号,第 k k k个点对应的虚数记作 ω n k \omega_n^k ωnk(根据复数相乘时模长相乘幅角相加可以看出, ω n k \omega_n^k ωnk ω n 1 \omega_n^1 ωn1 k k k次方,所以 ω n 1 \omega_n^1 ωn1被称为 n n n单位根)。

根据每个复数的幅角,可以计算出所对应的点/向量。 ω n k \omega_n^k ωnk对应的点/向量是 ( cos ⁡ k n 2 π , sin ⁡ k n 2 π ) (\cos\frac{k}{n}2\pi,\sin\frac{k}{n}2\pi) (cosnk2π,sinnk2π),也就是说这个复数是 cos ⁡ k n 2 π + i sin ⁡ k n 2 π \cos\frac{k}{n}2\pi+i\sin\frac{k}{n}2\pi cosnk2π+isinnk2π

傅里叶说:把 n n n个复数 ω n 0 , ω n 1 , ω n 2 , … , ω n n − 1 \omega_n^0,\omega_n^1,\omega_n^2,\dots,\omega_n^{n-1} ωn0,ωn1,ωn2,,ωnn1代入多项式,能得到一种特殊的点值表示,这种点值表示就叫离散傅里叶变换吧!

为什么要使用单位根作为 x x x代入

( y 0 , y 1 , y 2 , … , y n − 1 ) (y_0,y_1,y_2,\dots,y_{n-1}) (y0,y1,y2,,yn1)为多项式 A ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n − 1 x n − 1 A(x)=a_0+a_1x+a_2x^2+\dots+a_{n-1}x^{n-1} A(x)=a0+a1x+a2x2++an1xn1的离散傅里叶变换。

再设一个多项式 B ( x ) = y 0 + y 1 x + y 2 x 2 + ⋯ + y n − 1 x n − 1 B(x)=y_0+y_1x+y_2x^2+\dots+y_{n-1}x^{n-1} B(x)=y0+y1x+y2x2++yn1xn1

现在我们把上面的 n n n个单位根的倒数,即
ω n 0 , ω n − 1 , ω n − 2 , … , ω n − ( n − 1 ) \omega_{n}^{0},\omega_{n}^{-1},\omega_{n}^{-2},\dots,\omega_{n}^{-(n-1)} ωn0,ωn1,ωn2,,ωn(n1)作为 x x x代入到 B ( x ) B(x) B(x),得到了一个新的离散傅里叶变换 ( z 0 , z 1 , z 2 , … , z n − 1 ) (z_0,z_1,z_2,\dots,z_{n-1}) (z0,z1,z2,,zn1)

z k = ∑ i = 0 n − 1 y i ( ω n − k ) i = ∑ i = 0 n − 1 ( ∑ j = 0 n − 1 a j ( ω n i ) j ) ( ω n − k ) i = ∑ j = 0 n − 1 a j ( ∑ i = 0 n − 1 ( ω n j − k ) i ) \begin{aligned} z_k &=\sum_{i=0}^{n-1}y_i\left(\omega_n^{-k}\right)^i \\ &=\sum_{i=0}^{n-1}\left(\sum_{j=0}^{n-1}a_j\left(\omega_n^i\right)^j\right)\left(\omega_n^{-k}\right)^i \\ &=\sum_{j=0}^{n-1}a_j\left(\sum_{i=0}^{n-1}\left(\omega_n^{j-k}\right)^i\right) \end{aligned} zk=i=0n1yi(ωnk)i=i=0n1(j=0n1aj(ωni)j)(ωnk)i=j=0n1aj(i=0n1(ωnjk)i)

其中 ∑ i = 0 n − 1 ( ω n j − k ) \sum_{i=0}^{n-1}\left(\omega_{n}^{j-k}\right) i=0n1(ωnjk)是可求的:当 j − k = 0 j-k=0 jk=0时,它等于 n n n。其他时候通过等比数列和可知它等于
( ω n j − k ) n − 1 ω n j − k − 1 = ( ω n n ) j − k − 1 ω n j − k − 1 = 1 j − k − 1 ω n j − k − 1 = 0 \frac{\left(\omega_n^{j-k}\right)^n-1}{\omega_n^{j-k}-1}=\frac{\left(\omega_n^n\right)^{j-k}-1}{\omega_n^{j-k}-1}=\frac{1^{j-k}-1}{\omega_n^{j-k}-1}=0 ωnjk1(ωnjk)n1=ωnjk1(ωnn)jk1=ωnjk11jk1=0
那么 z k z_k zk就等于 n a k na_k nak,即:
a i = z i n a_i=\frac{z_i}{n} ai=nzi

一个结论

把多项式 A ( x ) A(x) A(x)的离散傅里叶变换结果作为另一个多项式 B ( x ) B(x) B(x)的系数,取单位根的倒数即 ω n 0 , ω n − 1 , ω n − 2 , . . . , ω n − ( n − 1 ) \omega_{n}^{0},\omega_{n}^{-1},\omega_{n}^{-2},...,\omega_{n}^{-(n-1)} ωn0,ωn1,ωn2,...,ωn(n1)作为 x x x代入 B ( x ) B(x) B(x),得到的每个数再除以 n n n,得到的是 A ( x ) A(x) A(x)的各项系数。

这实现了傅里叶变换的逆变换——把点值表示转换成多项式系数表示,这就是离散傅里叶变换神奇的性质。

4.快速傅里叶变换

虽然傅里叶发明了神奇的变换,能把多项式转换成点值表示又转换回来,但是……它仍然是暴力代入的做法,复杂度仍是 O ( n 2 ) O(n^2) O(n2)

于是,快速傅里叶变换应运而生。它是一种分治的傅里叶变换。

快速傅里叶变换的数学证明

同样的,设 A ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n − 1 x n − 1 A(x)=a_0+a_1x+a_2x^2+\dots+a_{n-1}x^{n-1} A(x)=a0+a1x+a2x2++an1xn1,现在为了求离散傅里叶变换,要把一个 x = ω n k x=\omega^k_n x=ωnk代入。

考虑将 A ( x ) A(x) A(x)的每一项按照下标奇偶分成两部分:

A ( x ) = ( a o + a 2 x 2 + ⋯ + a n − 2 x n − 2 ) + ( a 1 x + a 3 x 3 + ⋯ + a n − 1 x n − 1 ) A(x)=\left(a_o+a_2x^2+\dots+a_{n-2}x^{n-2}\right)+\left(a_1x+a_3x^3+\dots+a_{n-1}x^{n-1}\right) A(x)=(ao+a2x2++an2xn2)+(a1x+a3x3++an1xn1)

设两个多项式:
A 1 ( x ) = a 0 + a 2 x + . . . + a n − 2 x n 2 − 1 A_1(x)=a_0+a_2x+...+a_{n-2}x^{\frac{n}{2}-1} A1(x)=a0+a2x+...+an2x2n1

A 2 ( x ) = a 1 + a 3 x + . . . + a n − 1 x n 2 − 1 A_2(x)=a_1+a_3x+...+a_{n-1}x^{\frac{n}{2}-1} A2(x)=a1+a3x+...+an1x2n1

则:
A ( x ) = A 1 ( x 2 ) + x A 2 ( x 2 ) A(x)=A_1(x^2)+xA_2(x^2) A(x)=A1(x2)+xA2(x2)
假设 k < n 2 k<\frac{n}{2} k<2n,现在将 x = ω n k x=\omega^k_n x=ωnk代入:
A ( ω n k ) = A 1 ( ω n 2 k ) + ω n k A 2 ( ω n 2 k ) = A 1 ( ω n 2 k ) + ω n k A 2 ( ω n 2 k ) \begin{aligned}A\left(\omega_n^k\right)&=A_1\left(\omega_n^{2k}\right)+\omega_n^kA_2\left(\omega_n^{2k}\right)\\&=A_1\left(\omega_{\frac{n}{2}}^{k}\right)+\omega_n^kA_2\left(\omega_{\frac{n}{2}}^{k}\right)\end{aligned} A(ωnk)=A1(ωn2k)+ωnkA2(ωn2k)=A1(ω2nk)+ωnkA2(ω2nk)
那么对于 A ( ω n k + n 2 ) A\left(\omega^{k+\frac{n}{2}}_n\right) A(ωnk+2n)
A ( ω n k + n 2 ) = A 1 ( ω n 2 k + n ) + ω n k + n 2 A 2 ( ω n 2 k + n ) = A 1 ( ω n 2 k × ω n n ) + ω n k + n 2 A 2 ( ω n 2 k × ω n n ) = A 1 ( ω n 2 k ) − ω n k A 2 ( ω n 2 k ) \begin{aligned}A\left(\omega_n^{k+\frac{n}{2}}\right)&=A_1\left(\omega_n^{2k+n}\right)+\omega_n^{k+\frac{n}{2}}A_2\left(\omega_n^{2k+n}\right)\\&=A_1\left(\omega_{\frac{n}{2}}^{k}\times\omega_n^n\right)+\omega_n^{k+\frac{n}{2}}A_2\left(\omega_{\frac{n}{2}}^{k}\times\omega_n^n\right)\\&=A_1\left(\omega_{\frac{n}{2}}^{k}\right)-\omega_n^kA_2\left(\omega_{\frac{n}{2}}^{k}\right)\end{aligned} A(ωnk+2n)=A1(ωn2k+n)+ωnk+2nA2(ωn2k+n)=A1(ω2nk×ωnn)+ωnk+2nA2(ω2nk×ωnn)=A1(ω2nk)ωnkA2(ω2nk)
所以,如果我们能知道两个多项式 A 1 ( x ) A_1(x) A1(x) A 2 ( x ) A_2(x) A2(x)分别在 ( ω n 2 0 , ω n 2 1 , ω n 2 2 , … , ω n 2 n 2 − 1 ) \left(\omega_{\frac{n}{2}}^{0},\omega_{\frac{n}{2}}^{1},\omega_{\frac{n}{2}}^{2},\dots,\omega_{\frac{n}{2}}^{\frac{n}{2}-1}\right) (ω2n0,ω2n1,ω2n2,,ω2n2n1)的点值表示,就可以 O ( n ) O(n) O(n)求出 A ( x ) A(x) A(x) ω n 0 , ω n 1 , ω n 2 , ⋯ ω n n − 1 \omega_n^0,\omega_n^1,\omega_n^2,\dotsm\omega^{n-1}_{n} ωn0,ωn1,ωn2,ωnn1处的点值表示了。而 A 1 ( x ) A_1(x) A1(x) A 2 ( x ) A_2(x) A2(x)都是规模缩小了一半的子问题。分治边界是 n = 1 n=1 n=1,此时直接return

快速傅里叶变换的实现
typedef complex<double> cp;
const double PI=acos(-1);
cp omega(int n,int k)
{
    return cp(cos(2*PI*k/n),sin(2*PI*k/n));
}
void fft(cp  *a,int n,bool inv)
{
    if(n==1) 
    {
        return ;
    }
    static cp buf[N];
    int m=n/2;
    for(int i=0;i<m;i++)//将每一项按照奇偶分为两组
    { 
        buf[i]=a[2*i];
        buf[i+m]=a[2*i+1];
    }
    for(int i=0;i<n;i++)
    {
        a[i]=buf[i];
    }
    fft(a,m,inv); //递归处理两个子问题
    fft(a+m,m,inv);
    for(int i=0;i<m;i++)//枚举x,计算A(x)
    { 
        cp x=omega(n,i); 
        if(inv) 
        {
            x=conj(x);//conj是一个自带的求共轭复数的函数,精度较高。当复数模为1时,共轭复数等于倒数
        }
        buf[i]=a[i]+x*a[i+m];//根据之前推出的结论计算
        buf[i+m]=a[i]-x*a[i+m];
    }
    for(int i=0;i<n;i++)
    {
        a[i]=buf[i];
    }
}

inv表示这次用的单位根是否要取倒数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值