你最愿意做的哪件事,才是你的天赋所在

0%

FFT详解

FFT(快速傅里叶变换)

单位根

可理解为把复坐标轴上的单位圆分成了n份

性质1

证明

这里用到了复数相乘的性质,即模长相乘,夹角相加,因为模长都为1,所以不变,夹角相加。

性质2

证明:

性质3

IFFT(系数转点值)

普通的一个多项式方程可以这样表示

我们希望转换成点值,就需要在平面上寻找n个x,然后得到n个y。
就得到这个矩阵

如图

现在我们需要通过n个蓝色框的x来求出红色框中的y,然后得到n个$(x_i,y_i)$的点,然后进行O(n)点乘即可。
现在就要用到刚刚所说的单位根,把$w_0 - w_{n-1}$代入函数中作为参数$x_0 - x_{n-1}$得到如下的方程组

然后我们对其中一个进行求解得到

得到这个式子之后,我们就可以利用递归,一层层的求下去,然后log的复杂度求出我们想要的点值的y。
但如果是常规的递归的话我们需要每次传下去一个数组,类似归并的思想,空间上有所限制,然后就有了蝴蝶变换这个东西,观察下面这个图

这个图就是在递归的过程中的位置变换,我们发现,后序列相当于原序列的二进制反转后排序,然后根据这个规律就可以提前得知它应该在的位置,这样就不需要递归了。

rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));

这个用了动态规划,i代表位置,然后对照上图即可理解
然后就可以使用点进行乘法了

IDFT

我们得到点值后需要转化为多项式的系数值,其实过程是一样的

也就是把矩阵变为逆矩阵,在图上的话可以想象一开始是顺时针转,现在逆时针转动,实际上得到的矩阵是
QQ图片20200303231627.png
所以转回来的时候需要乘以1/n

FFT板子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct cp
{
double r,i;
complex(double _r = 0,double _i = 0)
{
r = _r; i = _i;
}
complex operator +(const complex &b)
{
return complex(r+b.r,i+b.i);
}
complex operator -(const complex &b)
{
return complex(r-b.r,i-b.i);
}
complex operator *(const complex &b)
{
return complex(r*b.r-i*b.i,r*b.i+i*b.r);
}
};
void FFT(cp *s,int inv){
for(int i=0;i<n;i++){
if(i<r[i])swap(s[i],s[r[i]]);
}
for(int len=2;len<=n;len*=2){
cp wn = cp(cos(pi*2.0/len),inv*sin(pi*2.0/len));
for(int st=0;st<n;st+=len){
cp w = cp(1.0,0.0);
for(int i=st;i<st+len/2;i++,w*=wn){
cp x=s[i];//偶次幂
cp y=w*s[i+len/2];//奇次幂
s[i]=x+y;
s[i+len/2]=x-y;
}
}
}
}

洛谷P3803

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pi acos(-1)
const int N = 2e6+10;
int f[N],g[N],r[N];
struct cp
{
double r,i;
cp(double _r = 0,double _i = 0)
{
r = _r; i = _i;
}
cp operator +(const cp &b)
{
return cp(r+b.r,i+b.i);
}
cp operator -(const cp &b)
{
return cp(r-b.r,i-b.i);
}
cp operator *(const cp &b)
{
return cp(r*b.r-i*b.i,r*b.i+i*b.r);
}
}F[N],G[N];
void FFT(cp *s,int n,int inv){
int bit=0;
while ((1<<bit)<n)bit++;
for(int i=0;i<n;i++){
if(i<r[i])swap(s[i],s[r[i]]);
}
for(int len=2;len<=n;len*=2){
cp wn = cp(cos(pi*2.0/len),inv*sin(pi*2.0/len));
for(int st=0;st<n;st+=len){
cp w = cp(1.0,0.0);
for(int i=st;i<st+len/2;i++,w=w*wn){
cp x=s[i];//偶次幂
cp y=w*s[i+len/2];//奇次幂
s[i]=x+y;
s[i+len/2]=x-y;
}
}
}
}
int main(){
int n,m;
scanf("%d %d",&n,&m);
for(int i=0;i<=n;i++)scanf("%d",&f[i]),F[i].r=f[i];
for(int i=0;i<=m;i++)scanf("%d",&g[i]),G[i].r=g[i];
int len = 1;
while(len<=n+m)len<<=1;
int bit=0;
while ((1<<bit)<len)bit++;
for(int i=0;i<len;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(bit-1));
FFT(F,len,1);
FFT(G,len,1);
for(int i=0;i<=len;i++){
F[i]=F[i]*G[i];
}
FFT(F,len,-1);
for(int i=0;i<=n+m;i++){
printf("%d ",(int)(F[i].r/len+0.5));
}
}
-------------你最愿意做的哪件事才是你的天赋所在-------------