FFT(Fast Fourier Transform)
快速傅里叶变换
引入
这里的很多的工程上的文章和学术性的文章都是以音频和图像的处理和生活中的实际用途还有纯数学角度讲的,但是作为一个OIer,我们一般是不会用到这些的,所以这里就不讲解什么时域频域转换和波的分析之类的东西,而是将如何在OI中应用。
概念与前置
人物了解
其实这里要讲的傅里叶变换和完整的傅里叶变换有一定的区别,我们下面来看看傅里叶变换的两个公式:
-
傅里叶变换
F ( ω ) = F [ f ( t ) ] = ∫ − ∞ ∞ f ( t ) e − i ω t d t F(\omega)=\mathcal{F}[f(t)]=\int\limits_{-\infty}^\infty f(t)e^{-i\omega t} dt F(ω)=F[f(t)]=−∞∫∞f(t)e−iωtdt -
傅里叶逆变换
f ( t ) = F − 1 [ F ( ω ) ] = 1 2 π ∫ − ∞ ∞ F ( ω ) e i ω t d ω f(t)=\mathcal{F}^{-1}[F(\omega)]=\frac{1}{2\pi} \int\limits_{-\infty}^\infty F(\omega)e^{i\omega t} d\omega f(t)=F−1[F(ω)]=2π1−∞∫∞F(ω)eiωtdω
其中的 F \mathcal{F} F代表傅里叶变换, F − 1 \mathcal{F}^{-1} F−1表示傅里叶逆变换。
下面扯一些和算法并无多大联系的知识。
如果不想看直接可以调到下一个标题
这两个变换公式中, f ( t ) f(t) f(t)为以 t t t为周期的周期函数,且 t t t还满足狄利赫里条件。
狄利赫里条件
内容概括:也就是在给定的周期函数在有限的区间内,只有有限个第一类间断点和有限个极大值和极小值。满足该条件就符合傅里叶级数收敛。
感觉每次学啥都是递归似的学习啊QWQ
傅里叶级数
内容概括:任何周期函数都可以用正弦函数和余弦函数构成的无穷级数来表示(选择正弦函数与余弦函数作为基函数是因为它们是正交的),后世称傅里叶级数为一种特殊的三角级数,根据欧拉公式,三角函数又能化成指数形式,也称傅立叶级数为一种指数级数。
- 傅里叶级数的收敛
设
f
f
f是周期为
2
π
2\pi
2π且在
[
−
π
,
π
]
[-\pi,\pi]
[−π,π]上的可积或绝对可积的函数,如果
f
f
f在
x
0
x_0
x0处存在倒数
f
′
(
x
0
)
f^{'}(x_0)
f′(x0),或是有两个有限的单侧倒数:
f
+
′
(
x
0
)
=
lim
t
→
0
+
f
(
x
0
+
t
)
−
f
(
x
0
)
t
f^{'}_+(x_0)=\lim_{t\rightarrow0^{+}}\frac{f(x_0+t)-f(x_0)}{t}
f+′(x0)=t→0+limtf(x0+t)−f(x0)
f
−
′
(
x
0
)
=
lim
t
→
0
+
f
(
x
0
−
t
)
−
f
(
x
0
)
−
t
f^{'}_-(x_0)=\lim_{t\rightarrow0^{+}}\frac{f(x_0-t)-f(x_0)}{-t}
f−′(x0)=t→0+lim−tf(x0−t)−f(x0)
那么
f
f
f的
F
o
u
r
i
e
r
\rm Fourier
Fourier级数在
x
0
x_0
x0处收敛于
f
(
x
0
)
f(x_0)
f(x0),如果
f
f
f在
x
0
x_0
x0处仅有两个有限的广义单侧导数:
lim
t
→
0
+
f
(
x
o
+
t
)
−
f
(
x
0
+
0
)
t
\lim_{t\rightarrow 0^+}\frac{f(x_o+t)-f(x_0+0)}{t}
t→0+limtf(xo+t)−f(x0+0)
lim
t
→
0
+
f
(
x
0
−
t
)
−
f
(
x
0
−
0
)
−
t
\lim_{t\rightarrow 0^+}\frac{f(x_0-t)-f(x_0-0)}{-t}
t→0+lim−tf(x0−t)−f(x0−0)
那么
f
f
f的
F
o
u
r
i
e
r
\rm Fourier
Fourier级数在
x
0
x_0
x0处收敛于
1
2
(
f
(
x
0
+
0
)
+
f
(
x
0
−
0
)
)
\frac{1}{2}(f(x_0+0)+f(x_0-0))
21(f(x0+0)+f(x0−0)).
傅里叶级数的三角式:
f
(
t
)
=
a
0
+
∑
n
=
1
∞
(
a
n
cos
(
n
ω
t
)
+
b
n
sin
(
n
ω
t
)
)
f(t)=a_0+\sum_{n=1}^{\infty}(a_n\cos(n\omega t)+b_n\sin(n\omega t))
f(t)=a0+n=1∑∞(ancos(nωt)+bnsin(nωt))
其中
ω
=
2
π
T
\omega = \frac{2\pi}{T}
ω=T2π,
T
T
T为函数周期。
a
n
a_n
an和
b
n
b_n
bn分别控制了正弦波的振幅与频率。说好的不讲这些的呢?
它还可以用复指数形式和积分形式来表示:
f
(
t
)
=
∑
n
=
−
∞
∞
F
n
e
i
n
ω
t
f(t)=\sum_{n=-\infty}^{\infty}F_ne^{in\omega t}
f(t)=n=−∞∑∞Fneinωt
F
n
=
1
T
∫
0
T
f
(
t
)
e
−
i
n
ω
t
d
t
F_n=\frac{1}{T}\int_0^T f(t)e^{-in\omega t} dt
Fn=T1∫0Tf(t)e−inωtdt
其中的
F
F
F就是周期函数
f
f
f的傅里叶级数(Fourier Series, FS)
再回到傅里叶变换的两个式子,里面的 F ( ω ) F(\omega) F(ω)称为 f ( t ) f(t) f(t)的象函数,而 f ( t ) f(t) f(t)被称为 F ( ω ) F(\omega) F(ω)的原象函数。
象函数与原象函数
拉普拉斯变换
内容概述:这是一种线性的积分变换,可以将一个有参数实数 t ( t ≥ 0 ) t(t≥0) t(t≥0)的函数变换成一个参数为复数的函数 S S S。
在拉普拉斯变换中对于 f ( t ) f(t) f(t)的双边拉普拉斯变换 F ( s ) F(s) F(s),我们称其中的 F ( ω ) F(\omega) F(ω)为 f ( t ) f(t) f(t)的象函数,而 f ( t ) f(t) f(t)则为 F ( ω ) F(\omega) F(ω)的原象函数。
跳出递归
下面开始由最简单的入手一步步讲解 F F T \rm FFT FFT
多项式
- 定义
这个应该来看的人都知道吧
我们把形如
a
0
+
a
1
x
+
a
2
x
2
+
⋯
+
a
n
−
1
x
n
−
1
a_0+a_1x+a_2x^2+\cdots+a_{n-1}x^{n-1}
a0+a1x+a2x2+⋯+an−1xn−1的式子称作多项式(其中
a
0
a_0
a0可以形式化的写作
a
0
x
0
a_0x^0
a0x0)。
其中的
a
0
a_0
a0~
a
1
a_1
a1叫做多项式的系数,我们将一个关于
x
x
x的多项式可以简记为
A
(
x
)
A(x)
A(x)。
多项式的通式表达:
A
(
x
)
=
∑
i
=
0
n
−
1
a
i
x
i
A(x)=\sum_{i=0}^{n-1}a_ix^i
A(x)=i=0∑n−1aixi
一些规定:
-
我们令 n n n为一个多项式的次数界,即次数范围,那么这个多项式中的任意一项的次数都要小于 n n n。
-
我们定义两个多项式的加减法如下(两个多项式加起来的次数界去较大的一个):
A ( x ) ± B ( x ) = ∑ i = 0 n − 1 ( a i ± b i ) x i A(x)\pm B(x)=\sum_{i=0}^{n-1}(a_i\pm b_i)x^i A(x)±B(x)=i=0∑n−1(ai±bi)xi
这个可以 O ( n ) O(n) O(n)的简单计算啦。
- 我们接下来定义两个多项式的卷积(好像也就是乘法吧):
A ( x ) ⨂ B ( x ) = ∑ i = 0 A n − 1 ∑ j = 0 B n − 1 a i b i ⋅ x i × j A(x)\bigotimes B(x)=\sum_{i=0}^{A_n-1}\sum_{j=0}^{B_n-1}a_ib_i \cdot x^{i\times j} A(x)⨂B(x)=i=0∑An−1j=0∑Bn−1aibi⋅xi×j
两个多项式的卷积的次数界为 ( A n + B n ) (A_n+B_n) (An+Bn)。
卷积式子也可以写成以下形式:
A
(
x
)
⨂
B
(
x
)
=
∑
i
=
0
n
−
1
(
∑
j
=
0
i
a
j
b
i
−
j
)
x
i
A(x)\bigotimes B(x)=\sum_{i=0}^{n-1}\left(\sum_{j=0}^ia_jb_{i-j}\right)x^i
A(x)⨂B(x)=i=0∑n−1(j=0∑iajbi−j)xi
那么从定义式的角度来看,朴素的算法只能 O ( n 2 ) O(n^2) O(n2)的计算,那么有没有更快一点的计算呢?我们接着看。
多项式的表达方法
- 第一种:系数表达法
也就是我们上面的定义中的方法,用一组系数 a 0 a_0 a0~ a 1 a_1 a1来表示一个多项式,我们可以将这个系数看作一个 n − 1 n-1 n−1维的向量,记作 a → = ( a 0 , a 1 , ⋯   , a n − 1 ) a^{\rightarrow}=(a_0,a_1,\cdots ,a_n-1) a→=(a0,a1,⋯,an−1) ,然后向量一般可以与矩阵联系起来(后面有用)。
- 第二种:点值表达法
因为一个多项式 A ( x ) A(x) A(x)的实质是关于 x x x的一个函数,而一个 n − 1 n-1 n−1次的函数可以由 n n n个点对 ( x , y ) (x,y) (x,y)确定其形式形态,所以我们可以运用这个性质来表示一个多项式。对于一个次数界为 n − 1 n-1 n−1的多项式,我们可以选取 n n n个 x x x,记作 ( x 0 , x 1 , ⋯   , x n − 1 ) (x_0,x_1,\cdots ,x_{n-1}) (x0,x1,⋯,xn−1),然后带入这个多项式,可以对应求出 n n n个 y y y,我们把它记作 ( y 0 , y 1 , ⋯   , y n − 1 ) (y_0,y_1,\cdots ,y_{n-1}) (y0,y1,⋯,yn−1),那么现在我们就得到了这个函数(多项式)上的 n n n个点对,我们就可以用这 n n n个点对 ( x i , y i ) (x_i,y_i) (xi,yi)来表示这个多项式。其实这也是离散化的表示一个函数(多项式)的方法。
然后我们将其形式化:
令
α
=
{
(
x
i
,
y
i
)
[
y
i
=
A
(
x
i
)
]
∣
i
∈
[
0
,
n
−
1
]
,
i
∈
Z
}
\alpha=\{(x_i,y_i)[y_i=A(x_i)]|i\in [0,n-1],i\in Z\}
α={(xi,yi)[yi=A(xi)]∣i∈[0,n−1],i∈Z}
这个
α
\alpha
α即为
A
(
x
)
A(x)
A(x)的点值表达式。
我们用这个点值表达式有什么用处呢?
点值表达式的方便之处在于我们将两个多项式相乘如果用系数表达式则是
O
(
n
2
)
O(n^2)
O(n2)的,而使用点值表达式,则只用把两个多项式对应
x
i
x_i
xi的点相乘即可,即变为
O
(
n
)
O(n)
O(n)的了。(这里的点相乘会用到复数的知识)。
复数
复数其实就是一种数域的扩展,为了解决一些实数解决不了的和平面几何等的问题。
于是上面的点便可以定义为复数域
C
C
C上的复数。
那么对于一个点
p
p
p,我们可以将其表示为
p
=
x
+
y
i
p=x+yi
p=x+yi,其中
i
i
i就是复数单位,其中
i
i
i满足
i
2
=
−
1
i^2=-1
i2=−1。
那么这个点的四则运算就可以定义如下(结合复数的运算):
对于两个点 p , q p,q p,q,其中 p = x p + y p i , q = x q + y q i p=x_p+y_pi,q=x_q+y_qi p=xp+ypi,q=xq+yqi,我们可以用 ( x p , y p ) (x_p,y_p) (xp,yp)来表示点 p p p。
-
加法 + + +
p + q = ( x p + x q , y p + y q ) p+q=(x_p+x_q,y_p+y_q) p+q=(xp+xq,yp+yq) -
减法 − - −
p − q = ( x p − x q , y p − y q ) p-q=(x_p-x_q,y_p-y_q) p−q=(xp−xq,yp−yq) -
乘法 × \times ×
p × q = ( x p × x q − y p × y q , x p × y q + x q × y p ) p\times q=(x_p\times x_q-y_p\times y_q,x_p\times y_q+x_q\times y_p) p×q=(xp×xq−yp×yq,xp×yq+xq×yp) -
除法 ÷ ÷ ÷
p q = ( x p × x q + y p × y q x q 2 + y q 2 , y p × x q − y q × x p x q 2 + y q 2 ) \frac{p}{q}=\left(\frac{x_p\times x_q+y_p\times y_q}{x_q^2+y_q^2},\frac{y_p\times x_q-y_q\times x_p}{x_q^2+y_q^2}\right) qp=(xq2+yq2xp×xq+yp×yq,xq2+yq2yp×xq−yq×xp) -
共轭复数 c o n j conj conj【百度百科】
c o n j ( p ) = ( x p , − y p ) conj(p)=(x_p,-y_p) conj(p)=(xp,−yp)
复数就介绍到这,我们的点值表达式中的点的运算就是如此(把点用复数表示了(复平面))。
多项式求值
那么我们对于一个
x
i
x_i
xi如何快速求出
A
(
x
)
A(x)
A(x)呢?
会的大佬可以跳过 Orz
先观察定义式(次数界为
n
n
n):
A
(
x
)
=
∑
i
=
0
n
−
1
a
i
x
i
A(x)=\sum_{i=0}^{n-1}a_ix^i
A(x)=i=0∑n−1aixi
最笨的方法
O
(
n
2
)
O(n^2)
O(n2),每次暴力计算
x
i
x^i
xi。
优化一点就是
O
(
n
l
o
g
2
n
)
O(nlog_2n)
O(nlog2n),利用快速幂。
再好一点的就是
O
(
n
)
O(n)
O(n)预处理
x
i
x^i
xi,然后
O
(
n
)
O(n)
O(n)计算。
那么有没有极致一点的直接 O ( n ) O(n) O(n)的方法呢?
我们就是用秦九韶算法(Horner法则)。
其实他的本质类似于分治递归处理。
我们将这个多项式展开:
A
(
x
)
=
a
0
+
a
1
x
+
a
2
x
2
+
a
3
x
3
+
⋯
+
a
n
−
1
x
n
−
1
A(x)=a_0+a_1x+a_2x^2+a_3x^3+\cdots +a_{n-1}x^{n-1}
A(x)=a0+a1x+a2x2+a3x3+⋯+an−1xn−1
每次提取一个
x
x
x,然后一直这么做下去,就得到如下变换。
a
0
+
x
⋅
(
a
1
+
a
2
x
+
a
3
x
2
+
⋯
+
a
n
−
1
x
n
−
2
)
a_0+x\cdot (a_1+a_2x+a_3x^2+\cdots +a_{n-1}x^{n-2})
a0+x⋅(a1+a2x+a3x2+⋯+an−1xn−2)
a
0
+
x
⋅
(
a
1
+
x
⋅
(
a
2
+
a
3
x
+
⋯
+
a
n
−
1
x
n
−
3
)
)
a_0+x\cdot (a_1+x\cdot (a_2+a_3x+\cdots +a_{n-1}x^{n-3}))
a0+x⋅(a1+x⋅(a2+a3x+⋯+an−1xn−3))
⋯
\cdots
⋯
的到最里面一层就是
⋯
(
a
n
−
2
+
a
n
−
1
x
)
\cdots (a_{n-2}+a_{n-1}x)
⋯(an−2+an−1x)
我们发现变成了一个一次的式子,那么将
x
i
x_i
xi带入,求出值为
v
0
v_0
v0,继续得到如下式子:
⋯
(
a
n
−
3
+
v
0
x
)
\cdots (a_{n-3}+v_0x)
⋯(an−3+v0x)
然后同样计算,直到最后一个,那么每次都是一个一次的式子,
O
(
1
)
O(1)
O(1)的计算,然后总的复杂度就是
O
(
n
)
O(n)
O(n)啦。
代码实现就很简单啦
int v=a[n-1];
for(int i=n-2;i>=0;i--){
v=v*x+a[i];
}
s m a l l t r i c k \mathcal {small\ trick} small trick
接下来我们就继续看多项式卷积,我们有如下卷积式子:
C
(
x
)
=
A
(
x
)
⨂
B
(
x
)
=
∑
i
=
0
A
n
−
1
∑
j
=
0
B
n
−
1
a
i
b
i
⋅
x
i
×
j
C(x)=A(x)\bigotimes B(x)=\sum_{i=0}^{A_n-1}\sum_{j=0}^{B_n-1}a_ib_i \cdot x^{i\times j}
C(x)=A(x)⨂B(x)=i=0∑An−1j=0∑Bn−1aibi⋅xi×j
表示为点值表达式就有 C ( x ) = { x i , A ( x i ) ⋅ B ( x i ) ∣ i ∈ [ 0 , n − 1 ] , i ∈ Z } C(x)=\{x_i,A(x_i)\cdot B(x_i)|i\in [0,n-1],i\in Z\} C(x)={xi,A(xi)⋅B(xi)∣i∈[0,n−1],i∈Z}
我们发现这样可以 O ( n ) O(n) O(n)的计算(前面的点值表达方式说过)。
但虽然计算过程中复杂度十分优秀,但是预处理呢?
我们一般是得到一个系数表达式,然后转换为点值表达式的朴素复杂度为 O ( n 2 ) O(n^2) O(n2),先枚举 n n n个 x i x_i xi,然后每次 O ( n ) O(n) O(n)的计算多项式的值。然后我们 O ( n ) O(n) O(n)的运算,但大多数时候最终的答案要为系数表达式,所以最朴素的转变回来的方式就是 O ( n 3 ) O(n^3) O(n3)高斯消元(也就是解一个知道 n n n个点的 n n n次方程)。
这个,,,,还不如暴力呢,emmmm。
那么有没有一种快速的变换来解决这种预处理,当然有啦,那就是-------------------------------> FFT
继续前置 ⋯ ⋯ \cdots \cdots ⋯⋯
插值-点值转换
我们继续定义一种对于多项式的运算,名叫 D F T ( A ( x ) ) DFT(A(x)) DFT(A(x))(其实有的读者就应该知道这就是 离散傅里叶变换$discrete\ Fourier\ transform $)
它的作用就是将系数表达式的 A ( x ) A(x) A(x)转换为点值表达式。
而我们同样的还要定义它的逆运算,为 I D F T ( A ( x ) ) IDFT(A(x)) IDFT(A(x)),作用为将一个点值表达式转换为系数表达式的转换。
那么多项式的卷积就可以如下计算:
-
D F T ( A ( x ) ) DFT(A(x)) DFT(A(x)) D F T ( B ( x ) ) DFT(B(x)) DFT(B(x))
-
C ( x ) = ∑ i = 0 n − 1 A ( x i ) ⋅ B ( x i ) C(x)=\sum_{i=0}^{n-1}A(x_i)\cdot B(x_i) C(x)=∑i=0n−1A(xi)⋅B(xi)
-
I D F T ( C ( x ) ) IDFT(C(x)) IDFT(C(x))
最后的答案就为 C ( x ) C(x) C(x)了。
但是如何快速进行 D F T DFT DFT与 I D F T IDFT IDFT呢?肯定不能用朴素的方法。
所以这里就要运用复数的神奇性质啦!
单位根
一种十分神奇且超级特殊的一类方程的根。
定义: 即对于满足方程 x n = 1 x^n=1 xn=1的 x x x,被称为 n n n次单位根。
但从表面来看, n n n为奇数时 x x x只有1,偶数时有 − 1 , 1 -1,1 −1,1,这样的根太少,且用处不大,那么我们就将目光投向复数。
重要的性质 : i 2 = − 1 i^2=-1 i2=−1, i 3 = − i i^3=-i i3=−i, i 4 = 1 , ⋯ i^4=1,\cdots i4=1,⋯
那么在复数上, x n = 1 x^n=1 xn=1就几乎有无数多组解,那么这些解就被称为 n n n次单位复数根。
因为有着 x n = 1 x^n=1 xn=1的性质,那么将其带入多项式,所有的 x i x^i xi都可以变为1,那不就好解多啦。
如何求得复数意义下 x n = 1 x^n=1 xn=1的解呢?
- 欧拉公式
$e^{xi}=\cos x+i\sin x $
其中一个特殊的性质:
(
cos
x
+
i
sin
x
)
k
=
cos
k
x
+
i
sin
k
x
(\cos x+i\sin x)^k=\cos kx+i\sin kx
(cosx+isinx)k=coskx+isinkx
证明:用三角恒等式,变形,然后推出
k
k
k在奇数和偶数时的通式证明即可。
那么欧拉公式如何得到的呢?
证明:
通过
T
a
y
l
o
r
Taylor
Taylor展开得到(有高数基础的应该知道,不知道的也可以百度):
e
x
=
1
+
x
+
x
2
2
!
+
⋯
+
x
n
n
!
+
⋯
e^x=1+x+\frac{x^2}{2!}+\cdots +\frac{x^n}{n!}+\cdots
ex=1+x+2!x2+⋯+n!xn+⋯
cos
x
=
1
−
x
2
2
!
+
x
4
4
!
−
x
6
6
!
+
⋯
\cos x=1-\frac{x^2}{2!}+\frac{x^4}{4!}-\frac{x^6}{6!}+\cdots
cosx=1−2!x2+4!x4−6!x6+⋯
sin
x
=
x
−
x
3
3
!
+
x
5
5
!
−
x
7
7
!
+
⋯
\sin x=x-\frac{x^3}{3!}+\frac{x^5}{5!}-\frac{x^7}{7!}+\cdots
sinx=x−3!x3+5!x5−7!x7+⋯
然后我们将其扩展到复数域上,由于 i = − 1 i=\sqrt{-1} i=−1
所以可以得到如下式子:
e
x
i
=
1
+
x
i
−
x
2
2
!
−
x
3
3
!
i
+
x
4
4
!
+
x
5
5
!
i
−
⋯
e^{xi}=1+xi-\frac{x^2}{2!}-\frac{x^3}{3!}i+\frac{x^4}{4!}+\frac{x^5}{5!}i-\cdots
exi=1+xi−2!x2−3!x3i+4!x4+5!x5i−⋯
变形得到:
=
(
1
−
x
2
2
!
+
x
4
4
!
−
⋯
 
)
+
i
⋅
(
x
−
x
3
3
!
+
x
5
5
!
−
⋯
 
)
=\left(1-\frac{x^2}{2!}+\frac{x^4}{4!}-\cdots\right)+i\cdot \left(x-\frac{x^3}{3!}+\frac{x^5}{5!}-\cdots\right)
=(1−2!x2+4!x4−⋯)+i⋅(x−3!x3+5!x5−⋯)
=
cos
x
+
i
sin
x
=\cos x+i \sin x
=cosx+isinx
于是得到证明。
如果我们将
x
x
x用
π
\pi
π来代替,就可以得到
e
i
π
=
cos
π
+
i
sin
π
=
−
1
+
i
×
0
=
−
1
e^{i\pi}=\cos \pi +i \sin \pi=-1+i\times 0=-1
eiπ=cosπ+isinπ=−1+i×0=−1
即一个神奇的公式就是
e
i
π
+
1
=
0
e^{i\pi}+1=0
eiπ+1=0
然后我们接着将
x
=
2
⋅
k
π
(
k
∈
Z
)
x=2\cdot k\pi(k\in Z)
x=2⋅kπ(k∈Z)
那么又能够得到
e
2
⋅
k
π
i
=
1
e^{2\cdot k\pi i}=1
e2⋅kπi=1,那么这里的
e
2
⋅
k
π
i
e^{2\cdot k\pi i}
e2⋅kπi不就正好符合我们的方程
x
n
=
1
x^n=1
xn=1吗?
然后我们令
x
=
e
2
k
π
i
n
(
k
∈
Z
)
x=e^{\frac{2k\pi i}{n}}(k\in Z)
x=en2kπi(k∈Z),然后我们记
ω
n
k
=
x
\omega_n^k=x
ωnk=x,那么这个就是一个
n
n
n次单位复数根。
而且可以证明这样的
n
n
n次单位复数根一定有
n
n
n个:
证明:
令
k
=
n
r
+
p
,
p
∈
[
0
,
n
−
1
]
k=nr+p,p\in[0,n-1]
k=nr+p,p∈[0,n−1],则
ω
n
k
=
e
2
k
π
i
n
=
e
2
(
n
r
+
p
)
π
i
n
=
e
2
n
r
π
i
n
+
2
p
π
i
n
=
e
2
n
r
π
i
n
×
e
2
p
π
i
n
=
1
r
×
e
2
p
π
i
n
=
e
2
p
π
i
n
\omega_n^k=e^{\frac{2k\pi i}{n}}=e^{\frac{2(nr+p)\pi i}{n}}=e^{\frac{2nr\pi i}{n}+\frac{2p\pi i}{n}}=e^{\frac{2nr\pi i}{n}}\times e^{\frac{2p\pi i}{n}}=1^r\times e^{\frac{2p\pi i}{n}}=e^{\frac{2p\pi i}{n}}
ωnk=en2kπi=en2(nr+p)πi=en2nrπi+n2pπi=en2nrπi×en2pπi=1r×en2pπi=en2pπi
有因为
p
p
p有
n
n
n个,那么这个根也就有
n
n
n个。
记这些根为 ω n 0 \omega_n^0 ωn0~ ω n n − 1 \omega_n^{n-1} ωnn−1,且它们在乘法意义下构成一个群,即满足 ω n i ω n j = ω n i + j = ω n ( i + j ) m o d    n \omega_n^i\omega_n^j=\omega_n^{i+j}=\omega_n^{(i+j)\mod n} ωniωnj=ωni+j=ωn(i+j)modn。
还有一个特殊的性质是
ω
n
−
1
=
ω
n
n
−
1
\omega_n^{-1}=\omega_n^{n-1}
ωn−1=ωnn−1
证明:
ω
n
−
1
=
1
×
ω
n
−
1
=
e
2
π
i
×
e
−
2
π
i
n
=
e
2
π
i
−
2
π
i
n
=
e
2
(
n
−
1
)
π
i
n
=
ω
n
n
−
1
\omega_n^{-1}=1\times \omega_n^{-1}=e^{2\pi i}\times e^{\frac{-2\pi i}{n}}=e^{2\pi i-\frac{2\pi i}{n}}=e^{\frac{2(n-1)\pi i}{n}}=\omega_n^{n-1}
ωn−1=1×ωn−1=e2πi×en−2πi=e2πi−n2πi=en2(n−1)πi=ωnn−1
三个引理
-
消去引理
当 d ≠ 0 d≠0 d̸=0时,有 ω d n d k = ω n k \omega_{dn}^{dk}=\omega_{n}^{k} ωdndk=ωnk
证明: ω d n d k = e 2 d k π i d n = e 2 k π i n = ω n k \omega_{dn}^{dk}=e^{\frac{2dk\pi i}{dn}}=e^{\frac{2k\pi i}{n}}=\omega_{n}^{k} ωdndk=edn2dkπi=en2kπi=ωnk -
折半引理
当 n > 0 n>0 n>0且 n n n为偶数, ( ω n k ) 2 = ω n 2 k (\omega_n^k)^2=\omega_{\frac{n}{2}}^k (ωnk)2=ω2nk,用文字描述就是 n n n个 n n n次单位根的平方的集合就等于 n 2 \frac{n}{2} 2n个 n 2 \frac{n}{2} 2n次单位根的集合。
证明: ( ω n k ) 2 = ( e 2 k π i n ) 2 = e 2 k π i n × 2 = e 2 k π i n 2 = ω n 2 k (\omega_n^k)^2=(e^{\frac{2k\pi i}{n}})^2=e^{\frac{2k\pi i}{n}\times 2}=e^{\frac{2k\pi i}{\frac{n}{2}}}=\omega_{\frac{n}{2}}^k (ωnk)2=(en2kπi)2=en2kπi×2=e2n2kπi=ω2nk -
求和引理
对于任意的 k ∈ [ 0 , n ] k\in [0,n] k∈[0,n],都有 ∑ j = 0 n − 1 ( ω n k ) j = 0 \sum_{j=0}^{n-1}(\omega_n^k)^j=0 ∑j=0n−1(ωnk)j=0
证明:用等比数列的和的公式,带入推导化简一下就出来了(公式太难打了就读者自己推一推吧QAQ)。
特殊的: ω n n = 1 \omega_n^n=1 ωnn=1
那么运用复数和单位根,可以将 D F T DFT DFT与 I D F T IDFT IDFT降到 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)的复杂度,下面会继续详解方法。
马上接近正题了,加油!
蝴蝶变换
其实也叫 C o o l e y − T u k e y Cooley-Tukey Cooley−Tukey算法,它是将 r a d i x − r radix-r radix−r算法选取 2 k 2^k 2k为基,(其实基可以在数据的大小内任意选,但是一般选 2 2 2来变换),然后来做变换,也就是按照下标的 2 k 2^k 2k进制来进行分治,它一般具有 l o g 2 k n log_{2^k}n log2kn的复杂度,但是只能处理大小为 2 k 2^k 2k的整倍数的数据。
例如我们以
2
2
2为基,来将一个多项式
A
(
x
)
A(x)
A(x)的系数分成
a
,
b
a,b
a,b两个集合。
令集合大小为
n
,
S
=
n
2
n,S=\frac{n}{2}
n,S=2n,那么就有:
a
=
{
2
S
a
1
+
2
S
−
2
a
2
+
⋯
+
2
a
S
−
1
+
a
S
}
,
b
=
{
b
1
+
2
b
2
+
⋯
+
2
S
b
S
}
a=\{2^{S}a_1+2^{S-2}a_2+\cdots +2a_{S-1}+a_S\},b=\{b_1+2b_2+\cdots +2^{S}b_S\}
a={2Sa1+2S−2a2+⋯+2aS−1+aS},b={b1+2b2+⋯+2SbS}(其中的系数只是表示下标被分治的标志)。
那么就可以使用蝴蝶变换来神奇的实现该操作,具体形式如下图:
其实我这个图画的不好,(mspaint好难用QAQ)。
我在这就不多说了,蝴蝶逆变换也就是将 ω n k \omega_n^k ωnk改为 1 2 ω n − k \frac{1}{2}\omega_n^{-k} 21ωn−k就是逆变换了。
逆变换的证明可以看这里超级好的大佬文章,用矩阵的方法证明。
那么代码上也就简单啦!
离散傅里叶变换
其实还有连续的傅里叶变换
利用前面的 c o o l e y − T u k e y cooley-Tukey cooley−Tukey算法,我们一般将基设置为 2 2 2,那么就是按照多项式的下标的奇偶性分类(前面提出过),所以我们记原多项式为 A ( x ) A(x) A(x),它被分成两个 ( A 0 ( x ) , A 1 ( x ) ) \left(A_0(x),A_1(x)\right) (A0(x),A1(x))(偶,奇),我们可以将其写成如下形式:
A
0
(
x
)
=
a
0
+
a
2
x
+
a
4
x
2
+
⋯
=
∑
i
=
0
n
2
−
1
a
2
⋅
i
(
x
i
)
A_0(x)=a_0+a_2x+a_4x^2+\cdots=\sum_{i=0}^{\frac{n}{2}-1}a_{2\cdot i}(x^i)
A0(x)=a0+a2x+a4x2+⋯=i=0∑2n−1a2⋅i(xi)
A
1
(
x
)
=
a
1
+
a
3
x
+
a
5
x
2
+
⋯
=
∑
i
=
0
n
2
−
1
a
2
⋅
i
+
1
(
x
i
)
A_1(x)=a_1+a_3x+a_5x^2+\cdots=\sum_{i=0}^{\frac{n}{2}-1}a_{2\cdot i+1}(x^i)
A1(x)=a1+a3x+a5x2+⋯=i=0∑2n−1a2⋅i+1(xi)
然后根据折半引理
A
(
x
)
=
A
0
(
x
2
)
+
x
⋅
A
1
(
x
2
)
A(x)=A_0(x^2)+x\cdot A_1(x^2)
A(x)=A0(x2)+x⋅A1(x2)
我们将单位根带入,可知所有的
x
i
x^i
xi,那么直接用系数与单位根就可以计算了,并且根据折半引理,可以每次按奇偶折半分治下去,复杂度为
O
(
l
o
g
n
)
O(logn)
O(logn),每次变换
O
(
n
)
O(n)
O(n),所以总的时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
所以对于 k < n 2 k<\frac{n}{2} k<2n,我们有:
A
(
ω
n
k
)
=
A
0
(
(
ω
n
k
)
2
)
+
ω
n
k
A
1
(
(
ω
n
k
)
2
)
A(\omega_n^k)=A_0((\omega_n^k)^2)+\omega_n^kA_1((\omega_n^k)^2)
A(ωnk)=A0((ωnk)2)+ωnkA1((ωnk)2)
=
A
0
(
ω
n
2
k
)
+
ω
n
k
A
1
(
ω
n
2
k
)
=A_0(\omega_{\frac{n}{2}}^k)+\omega_n^kA_1(\omega_{\frac{n}{2}}^k)
=A0(ω2nk)+ωnkA1(ω2nk)
=
∑
i
=
0
n
2
−
1
a
2
i
(
ω
n
2
k
i
)
+
ω
n
k
∑
i
=
0
n
2
−
1
a
[
2
i
+
1
]
(
ω
n
2
k
i
)
=\sum_{i=0}^{\frac{n}{2}-1}a_{2i}(\omega^{ki}_{\frac{n}{2}})+\omega_n^k\sum_{i=0}^{\frac{n}{2}-1}a_{[2i+1]}(\omega^{ki}_{\frac{n}{2}})
=i=0∑2n−1a2i(ω2nki)+ωnki=0∑2n−1a[2i+1](ω2nki)
还有一个性质就是
A
(
ω
n
n
2
+
k
)
=
A
0
(
ω
n
2
k
)
+
ω
n
n
2
+
k
A
1
(
ω
n
2
k
)
A(\omega_n^{\frac{n}{2}+k})=A_0(\omega_{\frac{n}{2}}^k)+\omega_n^{\frac{n}{2}+k}A_1(\omega_{\frac{n}{2}}^k)
A(ωn2n+k)=A0(ω2nk)+ωn2n+kA1(ω2nk)
又因为
ω
n
n
2
=
−
1
\omega_n^{\frac{n}{2}}=-1
ωn2n=−1
所以原式就为
A
(
ω
n
n
2
+
k
)
=
A
0
(
ω
n
2
k
)
−
ω
n
k
A
1
(
ω
n
2
k
)
A(\omega_n^{\frac{n}{2}+k})=A_0(\omega_{\frac{n}{2}}^k)-\omega_n^kA_1(\omega_{\frac{n}{2}}^k)
A(ωn2n+k)=A0(ω2nk)−ωnkA1(ω2nk)
所以另一半也就可以求出了。
那么这个问题就较好的解决了,在 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)的时间内。
离散傅里叶逆变换
I D F T ( I n v e r s e D i s c r e t e F o u r i e r T r a n s f o r m ) IDFT(Inverse\ Discrete\ Fourier\ Transform) IDFT(Inverse Discrete Fourier Transform)
就是 F F T FFT FFT的最后一步了(上一步转换为点值表达式后还要O(n)的对于项乘起来,然后在对于乘起来后的点值表达式再进行IDFT)。
IDFT它的实质就是解一个线性方程组,但不会像高斯消元一样,具体的说明可以看前面推荐过的这篇文章,因为数学公式太难打了
具体实现
根据蝴蝶变换和折半引理,分治递归处理是最直接的,但是有时效率较差,那么我们深入探究蝴蝶变换的过程发现,可以用迭代的方法,而基为 2 2 2的变换,实质就是基于二进制位,但是数组顺序会在变换后打乱,所以我们要提前翻转一下,下面看代码,代码中有些解释。
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define db double
using namespace std;
const int M=2e6+1e5+10;
const db pi=acos(-1);
struct complex{
db x,y;
complex(db a=0,db b=0):x(a),y(b){}
complex operator +(complex a){return complex(x+a.x,y+a.y);}
complex operator -(complex a){return complex(x-a.x,y-a.y);}
complex operator *(complex a){return complex(x*a.x-y*a.y,x*a.y+y*a.x);}
//定义复数类,也可使用自带的complex,但是速度会比手写的慢。
}a[M],b[M],w[M];
int r[M],len,lg;
void DFT(complex *a,int n,int f){
// for(int i=1,j=(n>>1),k;i<=n-2;i++){
// if(i<j)swap(a[i],a[j]);
// for(k=(n>>1);j>=k;j-=k,k>>=1);
// if(j<k)j+=k;
// }每次重新变换
for(int i=0;i<n;i++) if(i<r[i]) swap(a[i],a[r[i]]);//预处理变换后直接交换
for(int i=2;i<=n;i<<=1){
int now=i>>1;
complex wn=w[i];wn.y*=f;
//预处理了单位根,如果为逆变换,f=-1,然后将sin项乘以-1
//不预处理的话精度误差会大一点,每次算的话,wn=complex(cos(2*pi/i),f*sin(2*pi/i))
for(int j=0;j<n;j+=i){
complex w=complex(1,0),x,y;
for(int k=j;k<j+now;k++,w=w*wn){
x=a[k],y=w*a[k+now];//枚举奇数项和偶数项,然后奇数项乘以单位根的次方
a[k]=x+y;a[k+now]=x-y;//根据前面推导的式子进行加减
}
}
}
if(f==-1)for(int i=0;i<n;i++)a[i].x/=n;//其实逆变换每次除以2,可以简单写为最后除以n=2^s
}
void FFT(complex *a,complex *b,int n,int m){
DFT(a,len,1);DFT(b,len,1);//先将两个多项式转换为点值表达式
for(int i=0;i<=len;i++) a[i]=a[i]*b[i];//对应项相乘
DFT(a,len,-1);//逆变换回来
for(int i=0;i<=n+m;i++) printf("%d ",int(a[i].x+0.5));
//输出系数,要注意精度误差
}
int x,n,m;
int main(){
//输入两个0~n和0~m的多项式系数,然后求其卷积后输出
//洛谷FFT模板
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++) scanf("%d",&x),a[i].x=x;
for(int i=0;i<=m;i++) scanf("%d",&x),b[i].x=x;
for(len=1;len<=n+m;len<<=1,++lg);
for(int i=0;i<len;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(lg-1));//预处理变换
for(int i=2;i<=len;i<<=1)w[i]=complex(cos(2*pi/i),sin(2*pi/i));//预处理单位根
FFT(a,b,n,m);
return 0;
}
结束与例题
类似FFT的还有:
-
NTT(快速数论变换):可以解决FFT的精度问题,用于在取模意义下的卷积。
-
FWT(快速沃尔什变换):解决集合卷积的快速算法,【我的讲解】
-
FMT(快速莫比乌斯变换):和莫比乌斯反演有点关系,我也不清楚,好像也可以解决一些奇怪的集合卷积问题。
一些题目:
如果有错的地方请指出,Thanks♪(・ω・)ノ。