FFT快速傅里叶变换

FFT

我们从一个问题进行讨论 F F T FFT FFT,我们有两个多项式 A ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n − 1 x n − 1 , B ( x ) = b 0 + b 1 x + b 2 x 2 + ⋯ + b m − 1 x m − 1 A(x)=a_0+a_1x+a_2x^2+\dots+a_{n-1}x^{n-1},\\B(x)=b_0+b_1x+b_2x^2+\dots+b_{m-1}x^{m-1} A(x)=a0+a1x+a2x2++an1xn1,B(x)=b0+b1x+b2x2++bm1xm1我们现在要求 A ( x ) ⋅ B ( x ) A(x)\cdot B(x) A(x)B(x)。朴素算法是显而易见的,时间复杂度是 Θ ( n m ) \Theta(nm) Θ(nm)暴力相乘即可,但这个时间复杂度我们是接受不了的。这时我们可以引入一个点值表示法

前置知识

点值表示法

多项式通常有两种表示方法,一种是系数表示法即形如 P ( x ) = ∑ i = 0 n − 1 a i x i P(x)=\sum_{i=0}^{n-1}a_ix^i P(x)=i=0n1aixi的表示法,而点值表示法则是把 n n n个互不相同的数 x x x带入多项式 P ( x ) P(x) P(x)中的的值作为 y y y值确定 n n n个点,我们可以证明互不重合的 n n n个点可以唯一确定一个多项式。

单位根

对于 ω n = 1 \omega^n=1 ωn=1的解傅里叶得出的结论是,对于 ω n = 1 \omega^n=1 ωn=1这个方程的复数解为,在复平面上我们对单位圆从正实数轴开始 n n n等分如图,这 n n n n n n等分点表示的复数即为原方程的复数解。

AAAAiAzCLwAAACKD8AsAAIDIIPwCAAAgIoz5f8qFrg5urV77AAAAAElFTkSuQmCC (703×735)

我们可以得到以下性质

  • ω n k = cos ⁡ 2 k π n + i sin ⁡ 2 k π n \omega_n^k=\cos\frac{2k\pi}{n}+i\sin\frac{2k\pi}{n} ωnk=cosn2+isinn2
  • ∀ i ≠ j \forall i\neq j i=j i , j < n i,j<n i,j<n,有 ω n i ≠ ω n j \omega_n^i\neq\omega_n^j ωni=ωnj
  • ω 2 n 2 k = ω n k \omega_{2n}^{2k}=\omega_n^k ω2n2k=ωnk(折半引理)
  • ω n k = − ω n k + n 2 \omega_n^k=-\omega_n^{k+\frac n2} ωnk=ωnk+2n
  • ω n 0 = ω n n \omega_n^0=\omega_n^n ωn0=ωnn
  • ω n i ⋅ ω n j = ω n i + j \omega_n^i\cdot\omega_n^j=\omega_n^{i+j} ωniωnj=ωni+j

FFT推导

正变换

我们要把一个多项式转为点值表示法,朴素转是 Θ ( n 2 ) \Theta(n^2) Θ(n2),达不到我们的目的。这里我们要把 n n n转为一个 2 2 2的幂的数不够就补位系数补 0 0 0,这样我们可以把多项式分为两个部分
A ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n − 1 x n − 1 = ( a 0 + a 2 x 2 + ⋯ + a n − 2 x n − 2 ) + ( a 1 + a 3 x 3 + ⋯ + a n − 1 x n − 1 ) \begin{align}A(x)&=a_0+a_1x+a_2x^2+\dots+a_{n-1}x^{n-1}\\&=(a_0+a_2x^2+\cdots+a_{n-2}x^{n-2})+(a_1+a_3x^3+\dots+a_{n-1}x^{n-1})\end{align} A(x)=a0+a1x+a2x2++an1xn1=(a0+a2x2++an2xn2)+(a1+a3x3++an1xn1)
A 1 ( x ) = a 0 + a 2 x + ⋯ + a n − 2 x n 2 − 1 A_1(x)=a_0+a_2x+\cdots+a_{n-2}x^{\frac n2-1} A1(x)=a0+a2x++an2x2n1 A 2 ( x ) = a 1 + a 3 x 2 + ⋯ + a n − 1 x n 2 − 1 A_2(x)=a_1+a_3x^2+\dots+a_{n-1}x^{\frac n2-1} A2(x)=a1+a3x2++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)

我们可以带入 n n n n n n次单位根
k ∈ [ 0 , n 2 − 1 ] , 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 ) k ∈ [ n 2 − 1 , n − 1 ] , 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 k A 2 ( ω n 2 k ) k\in\bigg[0,\frac n2-1\bigg],A(\omega_n^k)=A_1(\omega_n^{2k})+\omega_n^kA_2(\omega_n^{2k})=A_1(\omega_{\frac n2}^{k})+\omega_n^kA_2(\omega_{\frac n2}^{k})\\ k\in\bigg[\frac n2-1,n-1\bigg],A(\omega_n^{k+\frac n2})=A_1(\omega_n^{2k+n})+\omega_n^{k+\frac n2}A_2(\omega_n^{2k+n})=A_1(\omega_{\frac n2}^{k})-\omega_n^kA_2(\omega_{\frac n2}^{k}) k[0,2n1],A(ωnk)=A1(ωn2k)+ωnkA2(ωn2k)=A1(ω2nk)+ωnkA2(ω2nk)k[2n1,n1],A(ωnk+2n)=A1(ωn2k+n)+ωnk+2nA2(ωn2k+n)=A1(ω2nk)ωnkA2(ω2nk)
这样我们可以通过分治 log ⁡ 2 n \log_2n log2n层,这样我们可以在 n log ⁡ 2 n n\log_2n nlog2n的时间里求出 n n n个单位根的多项式值,我们把两个多项式的每个单位根对应的多项式值算出来,把同一单位根的两个多项式的值相乘,便可以得到乘积多项式的点值表达式。但在一些数据优秀的 O J OJ OJ上我们并不能通过,所以我们需要一个蝴蝶变换来优化。

蝴蝶变换

我们会发现一个规律,一个项的最后拆分位置恰好为下标的二进制反过来,我们以 8 8 8项的多项式为例,模拟拆分的过程:

位置编号(二进制) 000 000 000 001 001 001 010 010 010 011 011 011 100 100 100 101 101 101 110 110 110 111 111 111
初始序列 x 0 x_0 x0 x 1 x_1 x1 x 2 x_2 x2 x 3 x_3 x3 x 4 x_4 x4 x 5 x_5 x5 x 6 x_6 x6 x 7 x_7 x7
一次拆分后 x 0 x_0 x0 x 2 x_2 x2 x 4 x_4 x4 x 6 x_6 x6 x 1 x_1 x1 x 3 x_3 x3 x 5 x_5 x5 x 7 x_7 x7
两次拆分后 x 0 x_0 x0 x 4 x_4 x4 x 2 x_2 x2 x 6 x_6 x6 x 1 x_1 x1 x 5 x_5 x5 x 3 x_3 x3 x 7 x_7 x7
三次拆分后 x 0 ( 000 ) x_{0(000)} x0(000) x 4 ( 100 ) x_{4(100)} x4(100) x 2 ( 010 ) x_{2(010)} x2(010) x 6 ( 110 ) x_{6(110)} x6(110) x 1 ( 001 ) x_{1(001)} x1(001) x 5 ( 101 ) x_{5(101)} x5(101) x 3 ( 011 ) x_{3(011)} x3(011) x 7 ( 111 ) x_{7(111)} x7(111)

我们就可以得到一个迭代版的 F F T FFT FFT这样是可以通过所有 O J OJ OJ的洗礼的。

逆变换

正变换我们其实是求了这样一个东西
[ 1 1 1 ⋯ 1 1 ω n 1 ω n 2 ⋯ ω n − 1 1 ω n 2 ω n 4 ⋯ ω 2 n − 2 ⋮ ⋮ ⋮ ⋱ ⋮ 1 ω n − 1 n − 1 ω n − 1 2 n − 2 ⋯ ω n − 1 ( n − 1 ) 2 ] [ a 0 a 1 a 2 ⋮ a n − 1 ] = [ A 0 A 1 A 2 ⋮ a n − 1 ] \begin{bmatrix} 1&1&1&\cdots&1\\ 1&\omega_n^1&\omega_n^2&\cdots&\omega^{n-1}\\ 1&\omega_n^2&\omega_n^4&\cdots&\omega^{2n-2}\\ \vdots&\vdots&\vdots&\ddots&\vdots\\ 1&\omega_{n-1}^{n-1}&\omega_{n-1}^{2n-2}&\cdots&\omega_{n-1}^{(n-1)^2} \end{bmatrix} \begin{bmatrix} a_0\\a_1\\a_2\\\vdots\\a_{n-1} \end{bmatrix}= \begin{bmatrix} A_0\\A_1\\A_2\\\vdots\\a_{n-1} \end{bmatrix} 11111ωn1ωn2ωn1n11ωn2ωn4ωn12n21ωn1ω2n2ωn1(n1)2 a0a1a2an1 = A0A1A2an1
由于这个矩阵的元素非常特殊,它的逆矩阵也有特殊的性质,就是每一项取倒数除以变换的长度就能得到它的逆矩阵。

我们可以得到
1 ω k = ω k − 1 = e − 2 π i k = cos ⁡ ( 2 π k ) + i sin ⁡ ( − 2 π k ) = cos ⁡ ( 2 π k ) − i sin ⁡ ( 2 π k ) \frac{1}{\omega_k}=\omega_k^{-1}=e^{-\frac{2\pi i}{k}}=\cos(\frac{2\pi}{k})+i\sin(-\frac{2\pi}k)=\cos(\frac{2\pi}k)-i\sin(\frac{2\pi}k) ωk1=ωk1=ek2πi=cos(k2π)+isin(k2π)=cos(k2π)isin(k2π)
只需要我们在正变换的函数里面多传一个系数,表示正/逆变换,在计算的时候乘上系数即可。

code

const double Pi=acos(-1);
struct _cp{
    double re,im;
    inline _cp operator + (const _cp b)const
    {return (_cp){re+b.re,im+b.im};}
    inline _cp operator - (const _cp b)const
    {return (_cp){re-b.re,im-b.im};}
    inline _cp operator * (const _cp b)const
    {return (_cp){re*b.re-im*b.im,re*b.im+im*b.re};}
    inline _cp operator / (const _cp b)const{
        _cp x=(_cp){re,im};double q=b.re*b.re+b.im*b.im;
        _cp y=(_cp){b.re/q,b.im/q};
        return x*y;
    }
};//自己写的复数运算
int rev[5000005];
void FFT(_cp *a,int n,int inv)//inv为逆变换系数
{
    int bit=0;
    while((1<<bit)<n)bit++;
    for(int i=0;i<n;i++){
        rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
        if(i<rev[i])swap(a[i],a[rev[i]]);
    }
    for(int mid=1;mid<n;mid*=2){
    	_cp temp=(_cp){cos(Pi/mid),inv*sin(Pi/mid)};
        for(int i=0;i<n;i+=mid*2){
            _cp omega=(_cp){1,0};
            for(int j=0;j<mid;j++,omega=omega*temp){
                _cp x=a[i+j],y=omega*a[i+j+mid];
                a[i+j]=x+y,a[i+j+mid]=x-y;
            }
        }
    }
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值