写这篇东西的原因
我就是不会FFT怎么了!我只会念法法塔
复数?
大佬嫌我不懂复数,觉得我太LJ了,于是便Diss我。
复数:形如
a
+
b
i
,
(
a
,
b
∈
R
)
a+bi,(a,b∈R)
a+bi,(a,b∈R)的数。
n次单位复数根:
(
w
n
)
n
=
1
(w_n)^n=1
(wn)n=1
n次单位复数根恰好有n个,即:
w
=
e
2
π
i
∗
k
n
,
(
k
∈
[
0
,
n
)
)
w=e^{\frac{2\pi i*k}{n}},(k∈[0,n))
w=en2πi∗k,(k∈[0,n))
其中,i为虚数单位。
e
u
i
=
c
o
s
(
u
)
+
i
∗
s
i
n
(
u
)
e^{ui}=cos(u)+i*sin(u)
eui=cos(u)+i∗sin(u)代表单位圆上的一点,原点与其所在射线
与
x
与x
与x的正半轴成角
u
u
u。
所以n次单位复数根可看作是一个指针转一圈,又回到(1,0)。
几个最重要的原理:
1.消去引理
w
d
n
d
k
=
w
n
k
w^{dk}_{dn}=w^k_n
wdndk=wnk
2.折半引理
如果n为偶数且n>0,则n次单位复数跟的平方 的集合就是n/2次单位复数根的集合。
用消去引理证明。
3.求和引理
∀
n
≥
1
,
k
≥
0
,
n
∤
k
\forall n≥1,k≥0,n\nmid k
∀n≥1,k≥0,n∤k,有
Σ
j
=
0
n
−
1
(
w
n
k
)
j
=
0
\Sigma_{j=0}^{n-1}(w_n^k)^j=0
Σj=0n−1(wnk)j=0
等比数列求和。
Σ
j
=
0
n
−
1
(
w
n
k
)
j
=
(
w
n
k
)
n
−
1
w
n
k
−
1
=
(
w
n
n
)
k
−
1
w
n
k
−
1
=
(
1
)
k
−
1
w
n
k
−
1
=
0
\Sigma_{j=0}^{n-1}(w_n^k)^j=\frac{(w_n^k)^n-1}{w_n^k-1}=\frac{(w_n^n)^k-1}{w_n^k-1}=\frac{(1)^k-1}{w_n^k-1}=0
Σj=0n−1(wnk)j=wnk−1(wnk)n−1=wnk−1(wnn)k−1=wnk−1(1)k−1=0
分母显然不为0,因为
n
∤
k
n\nmid k
n∤k.
一些基本概念
1.次数界:多项式的最高项的次数+1.
2.系数表示:
A
(
x
)
=
Σ
i
=
0
n
−
1
a
i
x
i
A(x)=\Sigma_{i=0}^{n-1}a_ix^i
A(x)=Σi=0n−1aixi
3*.点值和插值:
点值表示:由点值对组成的集合。
S
=
(
x
1
,
A
(
x
1
)
)
,
(
x
2
,
A
(
x
2
)
)
,
.
.
,
(
x
n
,
A
(
x
n
)
)
S={(x_1,A(x_1)),(x_2,A(x_2)),..,(x_n,A(x_n))}
S=(x1,A(x1)),(x2,A(x2)),..,(xn,A(xn))
一般地,一个次数界为n的多项式的点值表示,
∣
S
∣
≥
n
|S|≥n
∣S∣≥n
其中,每个x必须互不相同。
点值运算:根据多项式A(x)的系数表示,得到多项式的点值表示的过程。
插值运算:点值运算的逆运算。假设现有一个n个点值对的点值表达,那么我们可以确定唯一的一个次数界为n的多项式。
为什么?考虑高斯消元。不就是解一个一次方程组吗?
最简单的例子:两点确定一条直线,三点确定一条抛物线。
!!!4.FFT原理
考虑以下两个东西。
1.点值表示的多项式乘法。2.对乘法结果进行插值运算。
1.点值表示的多项式乘法
对于两个多项式A,B,它们是被两个相同个点值对表示的唯一多项式
确定一组x,则有
A
:
(
x
0
,
y
0
)
,
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
,
.
.
.
,
(
x
n
−
1
,
y
n
−
1
)
A:{(x_0,y_0),(x_1,y_1),(x_2,y_2),...,(x_{n-1},y_{n-1})}
A:(x0,y0),(x1,y1),(x2,y2),...,(xn−1,yn−1)
B
:
(
x
0
,
y
0
′
)
,
(
x
1
,
y
1
′
)
,
(
x
2
,
y
2
′
)
,
.
.
.
,
(
x
n
−
1
,
y
n
−
1
′
)
B:{(x_0,y'_0),(x_1,y'_1),(x_2,y'_2),...,(x_{n-1},y'_{n-1})}
B:(x0,y0′),(x1,y1′),(x2,y2′),...,(xn−1,yn−1′)
令
C
=
A
∗
B
C=A*B
C=A∗B,则可得到C的点值表示
C
:
(
x
0
,
y
0
∗
y
0
′
)
,
(
x
1
,
y
1
∗
y
1
′
)
,
(
x
2
,
y
2
∗
y
2
′
)
,
.
.
.
,
(
x
n
−
1
,
y
3
∗
y
n
−
1
′
)
C:{(x_0,y_0*y'_0),(x_1,y_1*y'_1),(x_2,y_2*y'_2),...,(x_{n-1},y_3*y'_{n-1})}
C:(x0,y0∗y0′),(x1,y1∗y1′),(x2,y2∗y2′),...,(xn−1,y3∗yn−1′)
显然,要用2n对来表示A,B,不然计算不了C。
显然时间复杂度:
O
(
n
)
O(n)
O(n)
2.对乘法结果进行插值运算
确定多项式C的系数。
进入正题
DFT
DFT:离散傅里叶变换(Discrete Fourier Transform)
进行点值操作!
有大佬将整个傅里叶变换从头到尾看一遍,由于我是菜鸡,看不懂。
目标:计算一个次数界为n的多项式
A
(
x
)
=
Σ
j
=
0
n
−
1
a
j
x
j
A(x)=\Sigma_{j=0}^{n-1}a_jx^j
A(x)=Σj=0n−1ajxj在n次单位复数根处的值(共n个)
说白了就是用n个n次单位复数根代入进去A(x),求出每一个
A
(
w
n
i
)
A(w_n^i)
A(wni)
令
y
i
=
A
(
w
n
i
)
=
Σ
j
=
0
n
−
1
a
j
w
n
i
j
y_i=A(w_n^i)=\Sigma_{j=0}^{n-1}a_jw_n^{ij}
yi=A(wni)=Σj=0n−1ajwnij
y即为a的DFT。记为
y
=
D
F
T
n
(
a
)
y=DFT_n(a)
y=DFTn(a)
//这个a是A的系数,记得这一点。
IDFT
IDFT:即逆DFT。
进行插值操作!
现已知有n个点值对的点值表示
S
=
(
x
0
,
y
0
)
,
(
x
1
,
y
1
)
,
.
.
.
,
(
x
n
−
1
,
y
n
−
1
)
S={(x_0,y_0),(x_1,y_1),...,(x_{n-1},y_{n-1})}
S=(x0,y0),(x1,y1),...,(xn−1,yn−1),可以确定一个次数界为n的唯一的多项式
A
(
x
)
A(x)
A(x)
结论:
a
i
=
1
n
Σ
j
=
0
n
−
1
y
j
w
n
−
i
j
a_i=\frac{1}{n}\Sigma_{j=0}^{n-1}y_jw_n^{-ij}
ai=n1Σj=0n−1yjwn−ij
证明:
实际上就是
y
=
V
n
⋅
a
⇒
a
=
y
⋅
V
n
−
1
y=V_n·a\Rightarrow a=y·V_n^{-1}
y=Vn⋅a⇒a=y⋅Vn−1
所以将插值运算改成y与
V
n
−
1
V_n^{-1}
Vn−1的乘积。
其中,
V
n
∗
V
n
−
1
=
B
a
s
e
V_n*V_n^{-1}=Base
Vn∗Vn−1=Base,其中
B
a
s
e
i
,
j
=
1
,
i
=
j
,
B
a
s
e
i
,
j
=
0
,
i
≠
j
Base_{i,j}=1,i=j,Base_{i,j}=0,i≠j
Basei,j=1,i=j,Basei,j=0,i̸=j
求证:
V
n
−
1
的
(
i
,
j
)
V_n^{-1}的(i,j)
Vn−1的(i,j)处的元素为
w
n
−
i
j
n
\frac{w_n^{-ij}}{n}
nwn−ij
证明?矩阵乘法学过没?
B
a
s
e
i
,
j
=
Σ
k
=
0
n
−
1
V
n
[
i
,
k
]
∗
V
n
−
1
[
k
,
j
]
=
Σ
k
=
0
n
−
1
w
n
i
k
∗
w
n
−
j
k
n
=
Σ
k
=
0
n
−
1
w
n
k
(
i
−
j
)
n
Base_{i,j}=\Sigma_{k=0}^{n-1} V_n[i,k]*V_n^{-1}[k,j]=\Sigma_{k=0}^{n-1}w_n^{ik}*\frac{w_n^{-jk}}{n}=\Sigma_{k=0}^{n-1}\frac{w_n^{k(i-j)}}{n}
Basei,j=Σk=0n−1Vn[i,k]∗Vn−1[k,j]=Σk=0n−1wnik∗nwn−jk=Σk=0n−1nwnk(i−j)
这个证明方法的主要思路:需要证明当
i
=
j
i=j
i=j时,
B
a
s
e
[
i
,
j
]
=
1
Base[i,j]=1
Base[i,j]=1,当
i
≠
j
i≠j
i̸=j时,
B
a
s
e
[
i
,
j
]
=
0
Base[i,j]=0
Base[i,j]=0
将式子划开,让i和j在同一个式子里扯上关系。
当
i
=
j
i=j
i=j时,显然,
B
a
s
e
[
i
,
j
]
=
1
Base[i,j]=1
Base[i,j]=1
否则,由求和引理,
B
a
s
e
[
i
,
j
]
=
0
Base[i,j]=0
Base[i,j]=0
应用到多项式乘法?
FFT做两个多项式A,B的乘法的过程,实际上就是将A,B分别DFT一下,即进行点值操作。
此时得到了点值对为2n的点值表示,
O
(
n
)
O(n)
O(n)扫一遍,
y
c
[
i
]
=
y
a
[
i
]
∗
y
b
[
i
]
y_c[i]=y_a[i]*y_b[i]
yc[i]=ya[i]∗yb[i]。
接着对C进行IDFT,完成插值操作。
FFT主要部分
1.FFT是基于分治策略的。二分。
为了方便计算,n必须满足一个大前提:n为2的正整数幂。
这种策略的优点:利用了复数单位根复数根的特殊性质。
A
0
(
x
)
=
a
0
+
a
2
x
+
a
4
x
2
+
.
.
.
+
a
n
−
2
x
n
/
2
−
1
A_0(x)=a_0+a_2x+a_4x^2+...+a_{n-2}x^{n/2-1}
A0(x)=a0+a2x+a4x2+...+an−2xn/2−1
A
1
(
x
)
=
a
1
+
a
3
x
+
a
5
x
2
+
.
.
.
+
a
n
−
1
x
n
/
2
−
1
A_1(x)=a_1+a_3x+a_5x^2+...+a_{n-1}x^{n/2-1}
A1(x)=a1+a3x+a5x2+...+an−1xn/2−1
次数界缩小了一半!
A
(
x
)
=
A
0
(
x
2
)
+
x
A
1
(
x
2
)
A(x)=A_0(x^2)+xA_1(x^2)
A(x)=A0(x2)+xA1(x2)
2.问题转化
原问题:求多项式
A
(
x
)
A(x)
A(x)在n个n次单位复数根
w
n
0
,
w
n
1
,
.
.
.
,
w
n
n
−
1
w_n^0,w_n^1,...,w_n^{n-1}
wn0,wn1,...,wnn−1的函数值。
现问题:求多项式
A
0
(
x
)
,
A
1
(
x
)
A_0(x),A_1(x)
A0(x),A1(x)在n个n次单位复数根的平方的函数值。
上面加粗的字是FFT的一个主要的内容。
首先出场的是折半引理。根据它,n个n次单位复数根的平方实际上是由n/2个n/2次单位复数根组成,每个根恰好出现2次。(无聊说一句,就像一个指针转了2圈)
3.得出DFT的大致步骤
(I)对于目前的
A
(
x
)
A(x)
A(x)得出
A
0
(
x
)
,
A
1
(
x
)
A_0(x),A_1(x)
A0(x),A1(x)的系数。
(II)求出
A
0
(
x
)
,
A
1
(
x
)
A_0(x),A_1(x)
A0(x),A1(x)的点值表示。
(III)
A
(
x
)
=
A
0
(
x
2
)
+
x
A
1
(
x
2
)
A(x)=A_0(x^2)+xA_1(x^2)
A(x)=A0(x2)+xA1(x2)
时间复杂度:
T
(
n
)
=
T
(
n
/
2
)
+
O
(
n
)
T(n)=T(n/2)+O(n)
T(n)=T(n/2)+O(n)即
O
(
n
l
o
g
n
)
O(n\ log\ n)
O(n log n)
4.IDFT操作
根据
a
i
=
1
n
Σ
j
=
0
n
−
1
y
j
w
n
−
i
j
a_i=\frac{1}{n}\Sigma_{j=0}^{n-1}y_jw_n^{-ij}
ai=n1Σj=0n−1yjwn−ij,直接套DFT,改某些地方即可。
(I)用
w
n
−
1
w_n^{-1}
wn−1替换
w
n
w_n
wn,再把计算结果的每个元素除以n。
时间复杂度:
T
(
n
)
=
T
(
n
/
2
)
+
O
(
n
)
T(n)=T(n/2)+O(n)
T(n)=T(n/2)+O(n)即
O
(
n
l
o
g
n
)
O(n\ log\ n)
O(n log n)
FFT的实现
上面讲了实现方法。现在侧重于实现过程。
1.点值表示的多项式乘法。
2.对乘法结果进行插值运算。
伪代码1
void FFT(a){
n=多项式a的次数界;
if(n=1)return a;
w_n=(cos(2pi*i/n),sin(2pi*i/n));
w_kn=1;
得到a0,a1的系数。
得到a0的DFT y0,a1的DFT y1
for k=0 to n/2-1{
y[k]=y0[k]+w_nk*y1[k];
y[k+n/2]=y0[k]-w_nk*y1[k];
}
return y;
}
解释一下最后的循环部分。
实际上就是求出a的DFT y。
通过{}里的2行神操作,可以得出来a的DFT y。
y
[
k
]
=
y
0
[
k
]
+
w
n
k
y
1
[
k
]
=
A
0
(
(
w
n
k
)
2
)
+
w
n
k
A
1
(
(
w
n
k
)
2
)
=
A
(
w
n
k
)
y[k]=y_0[k]+w_n^ky_1[k]=A_0((w_n^k)^2)+w_n^kA_1((w_n^k)^2)=A(w_n^k)
y[k]=y0[k]+wnky1[k]=A0((wnk)2)+wnkA1((wnk)2)=A(wnk)
y
[
k
+
n
/
2
]
=
y
0
[
k
]
−
w
n
k
y
1
[
k
]
=
y
k
[
0
]
+
w
n
k
+
n
/
2
y
1
[
k
]
=
A
(
w
n
k
+
n
/
2
)
y[k+n/2]=y_0[k]-w_n^ky_1[k]=y_k[0]+w_n^{k+n/2}y_1[k]=A(w_n^{k+n/2})
y[k+n/2]=y0[k]−wnky1[k]=yk[0]+wnk+n/2y1[k]=A(wnk+n/2)
看不懂第二个式子的,尝试理解下面这一段话。
减号,代表着正好转了半圈。
还记得上文的一句话么?(无聊说一句,就像一个指针转了2圈)
想象一下这个过程,在第k时刻和第k+(n/2)时刻,指针停留的位置是一样的!!!
所以
A
1
(
(
w
n
k
+
n
/
2
)
2
)
=
A
1
(
w
n
2
k
+
n
)
=
A
1
(
w
n
2
k
)
A_1((w_n^{k+n/2})^2)=A_1(w_n^{2k+n})=A_1(w_n^{2k})
A1((wnk+n/2)2)=A1(wn2k+n)=A1(wn2k)
这招挺妙的。
程序优化
上述做法不是很好。需要优化。
PS:感谢前来观看博客的大佬们,但是到这里为止,还是不能够写出代码的。请继续读完下一部分,谢谢。
因为,根据位移和起始点的规律,可以对已有数组直接计算,不需要递归。
考虑当前问题处理的是哪些系数:
尝试发现下表的规律:
将i转化为的二进制,然后反过来设这个数为rev(i),则在开始的时候,i和rev(i)换个位置即可。
即第rev(i)个位置放
a
i
a_i
ai
如何
O
(
n
)
O(n)
O(n)求rev(i)?
考虑这个式子:
r
e
v
(
i
)
=
r
e
v
(
i
d
i
v
2
)
d
i
v
2
+
(
i
a
n
d
1
)
∗
(
n
d
i
v
2
)
rev(i)=rev(i\ div\ 2)div\ 2+(i\ and\ 1)*(n\ div\ 2)
rev(i)=rev(i div 2)div 2+(i and 1)∗(n div 2)
这是什么意思?
假设求出了rev(i/2),那么在i/2后面添加一个数,就相当于rev(i/2)向后挪1位。显然rev(i/2)是偶数。因为i/2的二进制第一位是0。
至于rev(i)的第一位是什么?取决于i的最后一位。
目标:不用递归,顺着做。
方法:模拟递归的回溯过程,顺序保存求出的值。注意,转移的时候,间隔是一样的。
在回溯的过程中,两个次数界为k的多项式合并为一个次数界为2k的多项式。
最后合并出来的那个那n个值就是
A
(
w
n
0
)
,
A
(
w
n
1
)
,
.
.
.
,
A
(
w
n
n
−
1
)
A(w_n^0),A(w_n^1),...,A(w_n^{n-1})
A(wn0),A(wn1),...,A(wnn−1)。
IDFT也按照上述过程就好了,不要忘记所有的元素需要除以n。
程序实现
void FFT(Cpx *y,int opz){
int i,h,k;
fo(i,0,m-1)if(i<Rev[i])swap(y[i],y[Rev[i]]);
for(h=2;h<=m;h<<=1){
Cpx wn(cos(2*Pi/h),opz*sin(2*Pi/h));
for(i=0;i<m;i+=h){
Cpx w(1,0);
fo(k,i,i+(h>>1)-1){
Cpx u=y[k],t=w*y[k+(h>>1)];
y[k]=u+t;
y[k+(h>>1)]=u-t;
w=w*wn;
}
}
}
if(opz==-1){
fo(i,0,m-1)y[i].r/=m;
}
}
void Mul(LL *a,LL *b,LL *c){
int i;
fo(i,0,mo-1)f1[i]=Cpx(b[i],0);
fo(i,mo,m-1)f1[i]=Cpx(0,0);
fo(i,0,mo-1)f2[i]=Cpx(c[i],0);
fo(i,mo,m-1)f2[i]=Cpx(0,0);
FFT(f1,1);
FFT(f2,1);
fo(i,0,m-1)f1[i]=f1[i]*f2[i];
FFT(f1,-1);
fo(i,0,mo-1)a[i]=(LL)(f1[i].r+0.5)%mo;
}
参考资料
yl《快速傅里叶变换》
lyc《从多项式乘法到傅里叶变换》
附加些东西
FWT的功能:给出两个多项式
A
(
x
)
,
B
(
x
)
A(x),B(x)
A(x),B(x),求
C
(
x
)
=
∑
i
⊕
j
=
k
A
(
i
)
∗
B
(
j
)
C(x)=\sum_{i⊕j=k}A(i)*B(j)
C(x)=∑i⊕j=kA(i)∗B(j)
给出
⊕
=
x
o
r
/
o
r
/
a
n
d
⊕=xor/or/and
⊕=xor/or/and的变换和逆变换
xor:
t
f
(
A
)
=
(
t
f
(
A
0
)
+
t
f
(
A
1
)
,
t
f
(
A
0
)
−
t
f
(
A
1
)
)
tf(A)=(tf(A_0)+tf(A_1),tf(A_0)-tf(A_1))
tf(A)=(tf(A0)+tf(A1),tf(A0)−tf(A1))
u
t
f
(
A
)
=
(
u
t
f
(
A
0
+
A
1
)
2
,
u
t
f
(
A
0
−
A
1
)
2
)
utf(A)=(\frac{utf(A_0+A_1)}{2},\frac{utf(A_0-A_1)}{2})
utf(A)=(2utf(A0+A1),2utf(A0−A1))
or:
t
f
(
A
)
=
(
t
f
(
A
0
)
,
t
f
(
A
0
)
+
t
f
(
A
1
)
)
tf(A)=(tf(A_0),tf(A_0)+tf(A_1))
tf(A)=(tf(A0),tf(A0)+tf(A1))
u
t
f
(
A
)
=
(
u
t
f
(
A
0
)
,
u
t
f
(
A
1
)
−
u
t
f
(
A
0
)
)
utf(A)=(utf(A_0),utf(A_1)-utf(A_0))
utf(A)=(utf(A0),utf(A1)−utf(A0))
and:
t
f
(
A
)
=
(
t
f
(
A
0
)
+
t
f
(
A
1
)
,
t
f
(
A
1
)
)
tf(A)=(tf(A_0)+tf(A_1),tf(A_1))
tf(A)=(tf(A0)+tf(A1),tf(A1))
u
t
f
(
A
)
=
(
u
t
f
(
A
1
)
−
u
t
f
(
A
0
)
,
u
t
f
(
A
1
)
)
utf(A)=(utf(A_1)-utf(A_0),utf(A_1))
utf(A)=(utf(A1)−utf(A0),utf(A1))