前言
如果我们能用一种时间上比
O
(
n
2
)
O(n^2)
O(n2) 更优秀的方法来计算大整数(函数)的乘法,那就好了。快速傅里叶变换(FFT) 可以帮我们在
O
(
n
log
n
)
O(n\log n)
O(nlogn) 的时间内解决问题。
函数乘积
计算两个大整数之积时,我们发现
(
2
x
+
3
)
(
4
x
+
5
)
=
8
x
2
+
22
x
+
15
.
.
.
(
∗
)
23
×
45
=
1035
(2x+3)(4x+5)=8x^2+22x+15\quad...(*)\\ 23\times45=1035
(2x+3)(4x+5)=8x2+22x+15...(∗)23×45=1035
而如果我们把
(
∗
)
(*)
(∗) 式右边的每一位的系数看做一个数每位上的数码,正好得到了
1035
1035
1035。事实上,对于所有的多项式乘法,以上规律同样成立。
证明: (提示)考虑竖式乘法的过程,和多项式乘法的过程,它们的本质都是一样的。
这样,我们就把问题转换为:计算两个已知函数之积的函数的解析式。
复平面、单位圆
考虑
−
9
\sqrt{-9}
−9 的值。
−
9
=
−
1
×
9
=
3
−
1
.
\begin{aligned}\sqrt{-9}&=\sqrt{-1}\times\sqrt9=3\sqrt{-1}.\end{aligned}
−9=−1×9=3−1.
类似地,
∀
N
∈
Z
−
\forall N\in \Z_-
∀N∈Z− 我们都可以用类似的方法得到
N
=
−
N
×
−
1
\sqrt{N}=\sqrt{-N}\times\sqrt{-1}
N=−N×−1
引入虚数单位
i
\text{i}
i,使
i
2
=
−
1.
\text{i}^2=-1.
i2=−1. 这样我们就重新认识了数的范围,从实数扩充到复数。
一复数
a
+
b
i
a+b\text{i}
a+bi 中的
a
,
b
∈
R
a,b\in\R
a,b∈R,
a
a
a 是它的实数部分,
b
i
b\text{i}
bi 是虚数部分。若
b
=
0
b=0
b=0,则它是实数。复数服从实数的大部分运算法则。
若两个复数,它们的实数部分相等,虚数部分之和为
0
0
0,我们称它们互为 共轭复数。
我们知道,数轴上的每个点与每个实数一一对应。类似地,我们可以使用 复平面 上的点表示复数。复平面与平面直角坐标系类似,它的 x x x 轴单位长度为 1 1 1, y y y 轴单位长度为 i \text{i} i。复平面上的点之横纵坐标表示的数之和即为该点表示的数。比如, ( 1 , 2 ) (1,2) (1,2) 表示 1 + 2 i 1+2\text{i} 1+2i。
以圆点为圆心,
1
1
1 为半径,在复平面上作圆,如图所示。这个圆叫 单位圆。
在圆上任取一点
A
A
A,过此点作
x
x
x 轴的垂线段,垂足为
B
B
B。设
∠
A
O
B
=
θ
∠AOB=\theta
∠AOB=θ。易知
O
B
=
O
A
×
cos
θ
=
cos
θ
A
B
=
O
B
×
sin
θ
=
sin
θ
OB=OA\times\cos\theta=\cos\theta\\ AB=OB\times\sin\theta=\sin\theta
OB=OA×cosθ=cosθAB=OB×sinθ=sinθ
∴
A
(
cos
θ
,
sin
θ
)
.
\therefore A(\cos\theta,\sin\theta).
∴A(cosθ,sinθ). 这个
θ
\theta
θ 叫做
A
A
A 的 辐角。
单位根及其性质
-
∀ k , n ∈ N ∗ \forall k,n\in\N^* ∀k,n∈N∗ 有 ω n k × ω n 1 = ω n k + 1 \omega_n^k\times\omega_n^1=\omega_n^{k+1} ωnk×ωn1=ωnk+1;
证明:
如图所示,过点 A ( 1 , 0 ) A(1,0) A(1,0) 作圆内接 n n n 边形。设 ∠ A O A ′ = θ . ∠AOA'=\theta. ∠AOA′=θ. 则
A ′ ( cos θ , sin θ ) A ′ ′ ( cos 2 θ , sin 2 θ ) ( cos θ + sin θ × i ) × ( cos 2 θ + sin 2 θ × i ) = cos θ cos 2 θ + ( cos θ sin 2 θ + sin θ cos 2 θ ) × i − sin θ sin 2 θ = ( sin θ sin 2 θ + cos θ cos 2 θ ) + ( sin θ cos 2 θ + cos θ cos 2 θ ) × i A'(\cos\theta,\sin\theta)\\A''(\cos2\theta,\sin2\theta)\\\ \\\begin{aligned}&\quad(\cos\theta+\sin\theta\times\text{i})\times(\cos2\theta+\sin2\theta\times\text{i})\\ &=\cos\theta\cos2\theta+(\cos\theta\sin2\theta+\sin\theta\cos2\theta)\times\text{i}-\sin\theta\sin2\theta\\ &=(\sin\theta\sin2\theta+\cos\theta\cos2\theta)+(\sin\theta\cos2\theta+\cos\theta\cos2\theta)\times\text{i}\end{aligned} A′(cosθ,sinθ)A′′(cos2θ,sin2θ) (cosθ+sinθ×i)×(cos2θ+sin2θ×i)=cosθcos2θ+(cosθsin2θ+sinθcos2θ)×i−sinθsin2θ=(sinθsin2θ+cosθcos2θ)+(sinθcos2θ+cosθcos2θ)×i
由三角函数恒等变换式
sin ( α + β ) = sin α sin β − cos α cos β , cos ( α + β ) = sin α cos β + sin β cos α \sin(\alpha+\beta)=\sin\alpha\sin\beta-\cos\alpha\cos\beta,\\ \cos(\alpha+\beta)=\sin\alpha\cos\beta+\sin\beta\cos\alpha sin(α+β)=sinαsinβ−cosαcosβ,cos(α+β)=sinαcosβ+sinβcosα
知
原 式 = sin ( θ + 2 θ ) + cos ( θ + 2 θ ) × i = sin 3 θ + cos 3 θ × i \begin{aligned}原式&=\sin(\theta+2\theta)+\cos(\theta+2\theta)\times\text{i}\\ &=\sin3\theta+\cos3\theta\times\text{i}\end{aligned} 原式=sin(θ+2θ)+cos(θ+2θ)×i=sin3θ+cos3θ×i
换句话说, A ′ A' A′ 和 A ′ ′ A'' A′′ 表示的数相乘后得到了对应的 A ′ ′ ′ A''' A′′′。
设这个多边形的顶点为 { ω i 0 } , i ∈ [ 0 , n − 1 ] \{\omega_i^0\},\ i\in[0,n-1] {ωi0}, i∈[0,n−1]。那么我们一定有 ω n k × ω n 1 = ω n k + 1 , k ∈ [ 0 , n − 1 ] \omega_n^k\times\omega_n^1=\omega_n^{k+1},\ k\in[0,n-1] ωnk×ωn1=ωnk+1, k∈[0,n−1]
特别地,我们有 ω n n = ω n 0 \omega_n^n=\omega_n^0 ωnn=ωn0。它们叫做 n n n 次 单位根。 -
ω n 0 = ω n n = 1 \omega_n^0=\omega_n^n=1 ωn0=ωnn=1;
-
ω n 0 , ω n 1 , . . . , ω n n − 1 \omega_n^0,\omega_n^1,...,\omega_n^{n-1} ωn0,ωn1,...,ωnn−1 互不相同;
-
∀ k , n ∈ N ∗ \forall k,n\in\N^* ∀k,n∈N∗ 有 ω n k = ω 2 n 2 k \omega_n^k=\omega_{2n}^{2k} ωnk=ω2n2k;
证明一: 感性理解。一个 n n n 边形把单位圆分成了 n n n 个部分,取这 n n n 个圆弧的中点,顺次连接这 2 n 2n 2n 个点,得到一个新多边形。则 ω n k \omega_n^k ωnk 即表示 n n n 变形的第 k k k 个单位根,也表示 2 n 2n 2n 变形的第 2 k 2k 2k 个点。
证明二:
ω
2
n
2
k
=
cos
2
k
×
2
π
2
n
+
i
×
sin
2
k
×
2
π
2
n
=
cos
k
×
2
π
n
+
i
×
sin
k
×
2
π
n
=
ω
n
k
.
\begin{aligned}\omega_{2n}^{2k}&=\cos2k\times\frac{2\pi}{2n}+\text{i}\times\sin2k\times\frac{2\pi}{2n}\\ &=\cos k\times\frac{2\pi}n+\text{i}\times\sin k\times\frac{2\pi}n\\ &=\omega_n^k.\end{aligned}
ω2n2k=cos2k×2n2π+i×sin2k×2n2π=cosk×n2π+i×sink×n2π=ωnk.
- ∀ k , n ∈ N ∗ \forall k,n\in\N^* ∀k,n∈N∗ 有 ω n k + 2 n = − ω n k \omega_n^{k+\frac2n}=-\omega_n^k ωnk+n2=−ωnk;
证明一: 感性理解。乘以 ω n 2 \omega_n^2 ωn2 的意思其实就是在单位圆逆时针转半圈。单位圆上的一个点,逆时针转了半圈后到达的点,与原来的点关于原点对称。
证明二: ω n k + 2 n = ω n k × ω n 2 n = ω n k × ( cos π + i × sin π ) = ω n k × ( − 1 + 0 ) = − ω n k . \begin{aligned}\omega_n^{k+\frac2n}&=\omega_n^k\times\omega_n^{\frac2n}\\ &=\omega_n^k\times(\cos\pi+\text{i}\times\sin\pi)\\ &=\omega_n^k\times(-1+0)\\ &=-\omega_n^k.\end{aligned} ωnk+n2=ωnk×ωnn2=ωnk×(cosπ+i×sinπ)=ωnk×(−1+0)=−ωnk.
-
∀
k
,
n
∈
N
∗
\forall k,n\in\N^*
∀k,n∈N∗ 有
∑
i
=
0
n
−
1
(
ω
n
k
)
i
=
{
0
,
k
≠
0
n
,
k
=
0
\sum_{i=0}^{n-1}{(\omega_n^k)^i}=\begin{cases}0,k\neq0\\n,k=0\end{cases}
i=0∑n−1(ωnk)i={0,k̸=0n,k=0
证明: ( I ) (\text{I}) (I) 若 k ≠ 0 k\neq0 k̸=0,设 S = ∑ i = 0 n − 1 ( ω n k ) i . . . ( 1 ) S=\sum_{i=0}^{n-1}{(\omega_n^k)^i}\quad...(1) S=i=0∑n−1(ωnk)i...(1) 则 ω n k × S = ∑ i = 1 n ( ω n k ) i . . . ( 2 ) \omega_n^k\times S=\sum_{i=1}^{n}{(\omega_n^k)^i}\quad...(2) ωnk×S=i=1∑n(ωnk)i...(2)
( 2 ) − ( 1 ) (2)-(1) (2)−(1) 得 ( ω n k − 1 ) × S = ( ω n k ) n − 1 S = ( ω n n ) k − 1 ω n k − 1 = 1 − 1 ω n k − 1 = 0. \begin{aligned}(\omega_n^k-1)\times S&=(\omega_n^k)^n-1\\ S&=\frac{(\omega_n^n)^k-1}{\omega_n^k-1}\\ &=\frac{1-1}{\omega_n^k-1}\\ &=0.\end{aligned} (ωnk−1)×SS=(ωnk)n−1=ωnk−1(ωnn)k−1=ωnk−11−1=0.
(
II
)
(\text{II})
(II) 当
k
=
0
k=0
k=0 时,
原
式
=
∑
i
=
0
n
−
1
1
=
n
.
\begin{aligned}原式&=\sum_{i=0}^{n-1}{1}\\&=n.\end{aligned}
原式=i=0∑n−11=n.
快速傅里叶变换
显然地,一个 n n n 次多项式可以被 n + 1 n+1 n+1 个点唯一确定。
那么,我们可以在单位圆上取 n + 1 n+1 n+1 个单位根,代入已知的两个函数,得到 n + 1 n+1 n+1 对点,再把每对点相乘,得到结果函数上的 n + 1 n+1 n+1 个点,再求出结果函数。
设已知函数(合并后)为
f
(
x
)
=
∑
i
=
0
n
−
1
a
i
x
i
=
(
a
0
+
a
2
x
+
.
.
.
+
a
n
−
2
x
n
−
2
)
+
(
a
1
x
+
a
3
x
3
+
.
.
.
+
a
n
−
1
x
n
−
1
)
\begin{aligned}f(x)&=\sum_{i=0}^{n-1}{a_ix^i}\\ &=(a_0+a_2x+...+a_{n-2}x^{n-2})\\ &+(a_1x+a_3x^3+...+a_{n-1}x^{n-1})\end{aligned}
f(x)=i=0∑n−1aixi=(a0+a2x+...+an−2xn−2)+(a1x+a3x3+...+an−1xn−1)
设
g
(
x
)
=
a
0
+
a
2
x
+
.
.
.
+
a
n
−
2
x
n
2
−
1
h
(
x
)
=
a
1
+
a
3
x
+
.
.
.
+
a
n
−
1
x
n
2
−
1
\begin{aligned}g(x)&=a_0+a_2x+...+a_{n-2}x^{\frac{n}2-1}\\ h(x)&=a_1+a_3x+...+a_{n-1}x^{\frac{n}2-1}\end{aligned}
g(x)h(x)=a0+a2x+...+an−2x2n−1=a1+a3x+...+an−1x2n−1
则
f
(
x
2
)
=
g
(
x
)
+
x
×
h
(
x
2
)
f(x^2)=g(x)+x\times h(x^2)
f(x2)=g(x)+x×h(x2)
令
x
=
ω
n
k
x=\omega_n^k
x=ωnk,由 单位根的性质1 得
f
(
x
2
)
=
g
(
ω
n
2
k
)
+
ω
n
k
×
h
(
ω
n
2
k
)
=
g
(
ω
n
2
k
)
+
ω
n
k
×
h
(
ω
n
2
k
)
.
.
.
(
1
)
\begin{aligned}f(x^2)&=g(\omega_n^{2k})+\omega_n^k\times h(\omega_n^{2k})\\ &=g(\omega_{\frac{n}2}^k)+\omega_n^k\times h(\omega_{\frac{n}2}^k)\quad...(1)\end{aligned}
f(x2)=g(ωn2k)+ωnk×h(ωn2k)=g(ω2nk)+ωnk×h(ω2nk)...(1)
令
x
=
−
ω
n
k
=
ω
n
k
+
n
2
x=-\omega_n^k=\omega_n^{k+\frac{n}2}
x=−ωnk=ωnk+2n 得
f
(
x
2
)
=
g
(
ω
n
2
k
+
n
)
+
ω
n
k
+
n
2
×
h
(
ω
n
2
k
+
n
)
=
g
(
ω
n
2
k
)
+
ω
n
k
+
n
2
×
h
(
ω
n
2
k
)
=
g
(
ω
n
2
k
)
−
ω
n
k
×
h
(
ω
n
2
k
)
.
.
.
(
2
)
\begin{aligned}f(x^2)&=g(\omega_n^{2k+n})+\omega_n^{k+\frac{n}2}\times h(\omega_n^{2k+n})\\ &=g(\omega_n^{2k})+\omega_n^{k+\frac{n}2}\times h(\omega_n^{2k})\\ &=g(\omega_{\frac{n}2}^k)-\omega_n^k\times h(\omega_{\frac{n}2}^k)\quad...(2)\end{aligned}
f(x2)=g(ωn2k+n)+ωnk+2n×h(ωn2k+n)=g(ωn2k)+ωnk+2n×h(ωn2k)=g(ω2nk)−ωnk×h(ω2nk)...(2)
不难发现,
(
1
)
(1)
(1) 式与
(
2
)
(2)
(2) 式 互轭!换句话说,当你求出
(
1
)
(1)
(1) 式的值时,只需将虚数部分取相反数(时间复杂度为
O
(
1
)
O(1)
O(1))即得到了
(
2
)
(2)
(2) 式。
这样,我们就将问题的规模减为一半。同理,剩下的一半也可以使用类似方法,分为更小的两半……没错,这就是 分治,这样就将时间复杂度降为了
O
(
n
log
n
)
O(n\log n)
O(nlogn)。
快速傅里叶逆变换
设
{
y
i
}
\{y_i\}
{yi} 是
{
a
i
}
\{a_i\}
{ai} 的傅里叶变换,即在
{
ω
n
i
}
\{\omega_n^i\}
{ωni} 处的值;
设
{
c
i
}
\{c_i\}
{ci} 是
{
y
i
}
\{y_i\}
{yi} 在
{
ω
n
−
i
}
\{\omega_n^{-i}\}
{ωn−i} 的值。
则有
c
k
=
∑
i
=
0
n
−
1
y
i
(
ω
n
−
k
)
i
=
∑
i
=
0
n
−
1
(
∑
j
=
0
n
−
1
a
j
(
ω
n
k
)
j
)
⋅
(
ω
n
−
k
)
i
=
∑
i
=
0
n
−
1
a
j
×
∑
j
=
0
n
−
1
(
ω
n
j
−
k
)
i
\begin{aligned}c_k&=\sum_{i=0}^{n-1}{y_i(\omega_n^{-k})^i}\\ &=\sum_{i=0}^{n-1}{(\sum_{j=0}^{n-1}{a_j(\omega_n^k)^j})·(\omega_n^{-k})^i}\\ &=\sum_{i=0}^{n-1}{a_j\times\sum_{j=0}^{n-1}{(\omega_n^{j-k})^i}}\end{aligned}
ck=i=0∑n−1yi(ωn−k)i=i=0∑n−1(j=0∑n−1aj(ωnk)j)⋅(ωn−k)i=i=0∑n−1aj×j=0∑n−1(ωnj−k)i
根据 单位根的性质6 ,有且仅有一个
j
∈
[
0
,
n
−
1
]
j\in[0,n-1]
j∈[0,n−1] 使得
j
=
k
j=k
j=k。
∴
∀
j
\therefore \forall j
∴∀j 有且仅有一个
(
ω
n
j
−
k
)
i
=
n
{(\omega_n^{j-k})^i}=n
(ωnj−k)i=n有且仅有
n
−
1
n-1
n−1 个
(
ω
n
j
−
k
)
i
=
0
{(\omega_n^{j-k})^i}=0
(ωnj−k)i=0
∴
∑
j
=
0
n
−
1
(
ω
n
j
−
k
)
i
=
n
,
c
k
=
∑
i
=
0
n
−
1
a
j
×
∑
j
=
0
n
−
1
(
ω
n
j
−
k
)
i
=
a
k
×
n
,
a
k
=
c
k
n
.
\begin{aligned}\therefore\sum_{j=0}^{n-1}{(\omega_n^{j-k})^i}&=n,\\c_k&=\sum_{i=0}^{n-1}{a_j\times\sum_{j=0}^{n-1}{(\omega_n^{j-k})^i}}\\ &=a_k\times n,\\ a_k&=\frac{c_k}n.\end{aligned}
∴j=0∑n−1(ωnj−k)ickak=n,=i=0∑n−1aj×j=0∑n−1(ωnj−k)i=ak×n,=nck.
换句话说,经过快速傅里叶逆变换后的数组
{
c
i
}
\{c_i\}
{ci},除以
n
n
n 后就得到了结果
{
a
i
}
\{a_i\}
{ai}。(这里的
n
n
n 是指
c
c
c 数组的长度)
小结
使用快速傅里叶变换(FFT)计算两个函数之积的步骤如下:
- 分别对两个函数进行快速傅里叶变换;
- 将两组结果相乘;
- 对结果进行快速傅里叶逆变换,并将结果除以
n
n
n。
迭代快速傅里叶变换
经过上文的学习,容易写出以下代码(感谢 linjiayang2016 大佬的代码)
void fast_fast_tle(int limit,complex *a,int type){
if(limit==1)
return ;
complex a1[limit>>1],a2[limit>>1]; //*
for(int i=0;i<=limit;i+=2)
a1[i>>1]=a[i],a2[i>>1]=a[i+1];
fast_fast_tle(limit>>1,a1,type);
fast_fast_tle(limit>>1,a2,type);
complex Wn=complex(std::cos(2.0*Pi/limit),type*std::sin(2.0*Pi/limit)),w=complex(1,0);
for(int i=0;i<(limit>>1);i++,w=w*Wn)
a[i]=a1[i]+w*a2[i],a[i+(limit>>1)]=a1[i]-w*a2[i];
}
发现
∗
*
∗ 行定义了两个不小的数组,而 FFT
函数是被递归调用的,所以会造成爆栈。
原序列 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
快速傅里叶变换后序列 | 1 | 5 | 3 | 7 | 2 | 6 | 4 | 8 |
原下标的二进制 | 000 | 100 | 010 | 110 | 001 | 101 | 011 | 111 |
新下标的二进制 | 000 | 001 | 010 | 011 | 100 | 101 | 110 | 111 |
不难发现,快速傅里叶变换后的序列中,每个数的新下标的二进制,在数值上等于原下标二进制的反转。
设原下标为 x x x 的数的新下标为 r [ x ] r[x] r[x]。
以十进制数
184
184
184 的二进制表示为例。设这个二进制数长度为
l
l
l。
1
0
1
1
1
0
0
∣
0
\begin{array}{ccc}1&\ &0&\ &1&\ &1&\ &1&\ &0&\ &0&|&0\end{array}
1 0 1 1 1 0 0∣0
我们把这个数分为两部分,第一部分是前
l
−
1
l-1
l−1 位,第二部分是最后一位。不难得到,它的翻转就是 在第二部分后面接上第一部分的翻转形成的数。
即
r[i]=((r[i>>1]>>1)|((i&1)<<(l-1)));
至此,快速傅里叶变换的相关内容已全部结束。
luogu P3803 \text{luogu P3803} luogu P3803
给你两个正整数 n , m ≤ 1 0 6 n,m\leq10^6 n,m≤106,和 n + 1 n+1 n+1 次多项式 f ( x ) f(x) f(x)、 m + 1 m+1 m+1 次多项式 g ( x ) g(x) g(x)。求 f ( x ) f(x) f(x) 和 g ( x ) g(x) g(x) 的卷积。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define reg register
const int MAXN=400010;
const double pi=acos(-1.0);
struct compex{
double x,y;
friend compex operator +(const compex a,const compex b){
compex c;
c.x=a.x+b.x;c.y=a.y+b.y;
return c;
}
friend compex operator -(const compex a,const compex b){
compex c;
c.x=a.x-b.x;c.y=a.y-b.y;
return c;
}
friend compex operator *(const compex a,const compex b){
compex c;
c.x=a.x*b.x-a.y*b.y;
c.y=a.x*b.y+a.y*b.x;
return c;
}
}a[MAXN],b[MAXN];
int r[MAXN];
int n,m,l=0;
int limit=1;
void FFT(compex*t,int type){
for(reg int i=0;i<limit;++i)
if(i<r[i]) swap(t[i],t[r[i]]);
for(reg int mid=1;mid<limit;mid<<=1){
compex wn=(compex){cos(pi/mid),type*sin(pi/mid)};
for(reg int j=0,R=(mid<<1);j<limit;j+=R){
compex w={1.0,0};
for(reg int k=0;k<mid;++k,w=w*wn){
compex x=t[j+k],y=w*t[j+k+mid];
t[j+k]=x+y;
t[j+k+mid]=x-y;
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(reg int i=0;i<=n;++i)
scanf("%lf",&a[i].x);
for(reg int i=0;i<=m;++i)
scanf("%lf",&b[i].x);
while(limit<=n+m) limit<<=1,++l;
for(reg int i=1;i<limit;++i)
r[i]=((r[i>>1]>>1)|((i&1)<<(l-1)));
FFT(a,1);FFT(b,1);
for(reg int i=0;i<limit;++i)
a[i]=a[i]*b[i];
FFT(a,-1);
for(reg int i=0;i<=n+m;++i)
printf("%d ",(int)(a[i].x/limit+0.5));
}