最近舟游疯狂出货,心情很好~

FFT

FWT

 


 

快速傅里叶变换(FFT)

具体的推导见这篇:胡小兔 - 小学生都能看懂的FFT!!! (写的很好,不过本小学生第一次没看懂0.0)

总结下关键内容

 

~ Part 0 ~ 点值表示

对于一$n$项多项式$A(x)=a_0+a_1x+a_2x^2+...+a_{n-1}x^{n-1}$

我们代入$n$个不同的数$x_i$,得到$n$个值$y_i=A(x_i)$

则称这$n$个有序数对$(x_i,y_i)$为多项式$A(x)$的点值表示(可以认为是$xOy$平面上的点)

可以证明(略),通过这个$n$个点值可以还原出原多项式

 

~ Part 1 ~ 多项式表示转点值表示:

将原多项式$A(x)$的次数补成$2$的幂次,然后进行递归拆分

设当前一层的多项式为$B(x)$,项数为$n$,即:$B(x)=b_0+b_{1}x+b_{2}x^{2}+...+b_{n-1}x^{n-1}$

先将此多项式的按照奇偶项系数拆成两个式子

\[B_0(x)=b_0+b_2x+b_4x^2+...+b_{n-2}x^{\frac{n}{2}-1}\]

\[B_1(x)=b_1+b_3x+b_5x^2+...+b_{n-1}x^{\frac{n}{2}-1}\]

那么可以将$B(x)$重新表示:$B(x)=B_0(x^2)+xB_1(x^2)$

我们希望将复平面上单位圆的点代入,得到$B(\omega^{0}_{n})$,...,$B(\omega^{n-1}_{n})$的值

若$0\leq i<\frac{n}{2}$,则有【这里使用了复数运算律:因为$\omega$在单位圆上,所以$\omega^i_n\cdot\omega^j_n=\omega^{i+j}_n$;因为$n$为$2$的倍数,所以$\omega^{2i}_n=\omega^i_{\frac{n}{2}}$】

\begin{align*}B(\omega^i_n)&=B_0((\omega^i_n)^2)+\omega^i_nB_1((\omega^i_n)^2)\\&=B_0(\omega^{2i}_n)+\omega^i_nB_1(\omega^{2i}_n)\\&=B_0(\omega^i_{\frac{n}{2}})+\omega^i_nB_0(\omega^i_{\frac{n}{2}})\end{align*}

而对于单位圆上的另一半,则有【这里追加一个复数运算律:因为$n$为$2$的倍数,所以$\omega^{i+\frac{n}{2}}_n=-\omega^i_n$】

\begin{align*}B(\omega^{i+\frac{n}{2}}_n)&=B_0((\omega^{i+\frac{n}{2}}_n)^2)+\omega^{i+\frac{n}{2}}_nB_1((\omega^{i+\frac{n}{2}}_n)^2)\\&=B_0(\omega^{2i+n}_n)-\omega^i_nB_1(\omega^{2i+n}_n)\\&=B_0(\omega^i_{\frac{n}{2}})-\omega^i_nB_0(\omega^i_{\frac{n}{2}})\end{align*}

所以我们将这一层计算$B(\omega^0_n)$,...,$B(\omega^{n-1}_{n-1})$的问题变为计算下一层$B_0(\omega^0_{\frac{n}{2}})$,...,$B_0(\omega^{\frac{n}{2}-1}_{\frac{n}{2}})$,$B_1(\omega^0_{\frac{n}{2}})$,...,$B_1(\omega^{\frac{n}{2}-1}_{\frac{n}{2}})$

这样,总的复杂度是$O(N logN)$

 

~ Part 2 ~ 点值转多项式系数

设原多项式为$A(x)=a_0+a_1x+a_2x^2+...+a_{n-1}x^{n-1}$,其中补成$2$的幂次项后项数为$n$

则分别代入$\omega^0_n$,$\omega^1_n$,...,$\omega^{n-1}_n$,得到$y_i=A(\omega^i_n)$,$i=0,1,...,n-1$,即上面得到的$n$个点值

令$B(x)=y_0+y_1x+y_2x^2+...+y_{n-1}x^{n-1}$

再分别代入$\omega^0_n$,$\omega^{-1}_n$,...,$\omega^{-(n-1)}_n$,运算(这里略去)得到$a_i=\frac{B(\omega^{-i}_n)}{n}$,$i=0,1,...,n-1$

 

按这个思路写出的代码如下(实现了多项式系数转点值,再转回多项式系数)

大佬用的数组指针确实方便

#include <cstdio>
#include <complex>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;

typedef complex<double> cp;
const int N=100005;
const double pi=acos(-1.0);

inline cp omega(int x,int n,int rev)
{
    if(rev)
        x=-x;
    return cp(cos(x*2*pi/(double)n),sin(x*2*pi/(double)n));
}

cp tmp[N];

void fft(cp *a,int n,int rev)
{
    if(n==1)
        return;
    
    for(int i=0;i<n;i++)
        tmp[i]=a[i];
    for(int i=0;i<n/2;i++)
    {
        a[i]=tmp[i*2];
        a[n/2+i]=tmp[i*2+1];
    }
    
    fft(a,n/2,rev);
    fft(a+n/2,n/2,rev);
    
    for(int i=0;i<n/2;i++)
    {
        cp x=omega(i,n,rev);
        tmp[i]=a[i]+x*a[n/2+i];
        tmp[n/2+i]=a[i]-x*a[n/2+i];
    }
    
    for(int i=0;i<n;i++)
        a[i]=tmp[i];
}

int n;
cp a[N];

int main()
{
//    freopen("input.txt","r",stdin);
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        int x;
        scanf("%d",&x);
        a[i]=cp((double)x,0);
    }
    
    int sz=1;
    while(sz<n)
        sz<<=1;
    n=sz;
    
    fft(a,n,0);
    fft(a,n,1);
    
    for(int i=0;i<n;i++)
        printf("%.2lf ",a[i].real()/(double)n);
    printf("\n");
    return 0;
}
View Code

相关文章:

  • 2021-11-30
  • 2021-09-17
  • 2021-10-06
  • 2022-01-07
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2021-08-19
  • 2022-12-23
  • 2021-06-10
  • 2022-12-23
  • 2021-12-29
  • 2022-12-23
相关资源
相似解决方案