快速傅里叶变换FFT(模板)

时间:2021-04-22 14:50:11

好不容易闲下来总结一下FFT。QAQ

 

 

1.DFT:

对于多项式的乘法,DFT给了我们新的思路(点值表达式的O(n)相乘)

对于我们习惯的多项式算法例如多项式A(x)=5x+1和B(x)=2x+2

C(x)=A(x)*B(x)=(5x+1)*(2x+2)=10x2+12x+2

这就是系数表达式,非常地熟悉。

然而这里还有另外一种表达多项式的方法A(x)={(0,1),(2,11)},B(x)={(0,2),(2,6)}

其中(0,1)表示A(x)在x=0时值为1

发现C(x)可以直接由A(x)和B(x)的各个相等x值的表达值相乘得到

也就是说C(x)在x=0是就是等于1*2=2而x=2时的确等于6*11=66嗯嗯

但是有一个问题,那就是C(x)是2次的也就是说至少3个点值才可唯一确定C(x)

但是点值其实是无穷的,我们只是为了简化问题而选择了最少能确定表达式的特殊点

为了唯一确定更高次的表达式

所以A(x)被扩展成{(0,1),(1,6),(2,11)}而B(x)则为{(0,2),(1,4),(2,6)}

这样来表示C(x)就成了C(x)={(0,2),(1,24),(2,66)}表达式就被唯一确定了

我们发现虽然乘法过程是O(n)的但取点值时已经O(n2)了

2.FFT

同样是O(n2)算法

由于卷积模拟过程中的对应是唯一的,也就是说并没有重复,所以说可谓是不可简化的(至少我不会)

而对于DFT的算法其时间主要被消耗在取点值计算的过程中,但我们来仔细观察一下这个步骤发现其运算基本是完全重复的

而我们只需要使用某些玄学的东西就可以在更短的时间内求出这些点值。

其实点值表达式的x可以为复数

这个就是单位复数根Wn(数学内容自行百度),拥有一些美好的性质。

这个数的模长为1且其n次幂为1,理解成一个在复平面旋转的向量好了

拥有折半原理可以缩小问题规模至一半,就是分治搞定好了时间复杂度瞬间降到了O(nlogn)

而递归的常数会将这个算法的优势大大削减,所以我们找到一个最小的大于问题规模的2的次幂来非递归分治处理

这样做就把多项式转化为A(x)={....(wnk,val)..}的形式了

注意精度误差+0.5就好了

以LUOGUP3803为例:

 1 #include<cmath>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 const double Pi=acos(-1.0);
 6 struct cp{
 7     double x,y;
 8     cp friend operator + (cp a,cp b)
 9     {
10         return (cp){a.x+b.x,a.y+b.y};
11     }
12     cp friend operator - (cp a,cp b)
13     {
14         return (cp){a.x-b.x,a.y-b.y};
15     }
16     cp friend operator * (cp a,cp b)
17     {
18         return (cp){a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x};
19     }
20 }A[100000000],B[10000000];
21 int R[10000000];
22 int lim,n,m;
23 int l;
24 int cnta,cntb;
25 void FFt(cp *l,double f)
26 {
27     for(int i=0;i<lim;i++)
28         if(i<R[i])
29             std::swap(l[i],l[R[i]]);
30     for(int i=1;i<lim;i<<=1)
31     {
32         cp Wn;
33         Wn=(cp){cos(Pi/i),f*sin(Pi/i)};
34         for(int j=0;j<lim;j+=(i<<1))
35         {
36             cp W;
37             W=(cp){1.00,0.00};
38             for(int k=0;k<i;k++,W=W*Wn)
39             {
40                 cp nx,ny;
41                 nx=l[j+k];
42                 ny=l[i+j+k]*W;
43                 l[j+k]=nx+ny;
44                 l[i+j+k]=nx-ny;
45             }
46         }
47     }
48 }
49 int main()
50 {
51     l=0;
52     lim=1;
53     scanf("%d%d",&n,&m);
54     for(int i=0;i<=n;i++)
55     {
56         scanf("%lf",&A[i].x);
57     }
58     for(int i=0;i<=m;i++)
59     {
60         scanf("%lf",&B[i].x);
61     }
62     while(lim<=(n+m))
63     {
64         lim=lim<<1;
65         l++;
66     }
67     for(int i=0;i<lim;i++)
68     {
69         R[i]=(R[i>>1]>>1)|((i&1)<<(l-1));
70     }
71     FFt(A,1.00);
72     FFt(B,1.00);
73     for(int i=0;i<=lim;i++)
74     {
75         A[i]=A[i]*B[i];
76     }
77     FFt(A,-1.00);
78     for(int i=0;i<=n+m;i++)
79         printf("%d ",(int)(A[i].x/lim+0.5));
80     return 0;
81 }