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

0%

FFT详解

FFT(快速傅里叶变换)

单位根

wn=cos(2Πn)+sin(2Πn)i

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

性质1

wkn=w2k2n

证明

w2k2n=cos(2Π2k2n)+sin(2Π2k2n)i=cos(2Πkn)+sin(2Πkn)i=wkn

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

性质2

wk+n2n= wkn

证明:

wn2n=cos(π)+sin(π)i=1
wk+n2n=wn2nwkn= wkn

性质3

(win)2=(wi+n2n)2=w2in=win2

IFFT(系数转点值)

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

(x0x1x2xn1)(a1a2a3an1)=0

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

(x10x11x12x1n1x20x21x22x2n1x30x31x32x3n1xn10xn11xn12xnn1)(a1a2a3an1)=(y1y2y3yn1)

如图

现在我们需要通过n个蓝色框的x来求出红色框中的y,然后得到n个(xi,yi)的点,然后进行O(n)点乘即可。
现在就要用到刚刚所说的单位根,把w0wn1代入函数中作为参数x0xn1得到如下的方程组

{f(w0n)=a0+a1w0n+a2w0n++an1w0nf(w1n)=a0+a1w1n+a2w1n++an1w1nf(w2n)=a0+a1w2n+a2w2n++an1w2nf(wn1n)=a0+a1wn1n+a2wn1n++an1wn1n

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

{f(win)=a0+a1win+a2(win)2+a3(win)3+++an1(win)n1=a0+a2(win)2+a4(win)4++an2(win)n2+win(a1+a3(win)2+a5(win)4+an1(win)n2)=a0+a2(win2)+a4(win2)2++an2(win2)n22+win(a1+a3(win2)+a5(win2)2+an1(win2)n22)

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

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

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

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

IDFT

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

(a1a2a3an1)=(y1y2y3yn1)(x10x11x12x1n1x20x21x22x2n1x30x31x32x3n1xn10xn11xn12xnn1)1

也就是把矩阵变为逆矩阵,在图上的话可以想象一开始是顺时针转,现在逆时针转动,实际上得到的矩阵是
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));
}
}
-------------你最愿意做的哪件事才是你的天赋所在-------------
表情 | 预览
Powered By Valine
v1.3.10