前置知识:
1.点值表示
设 A ( x ) A(x) A(x)是一个 ( n − 1 ) (n - 1) (n−1)次多项式,那么把 ( n ) (n) (n)个不同的 ( x ) (x) (x)代入,会得到 ( n ) (n) (n)个 ( y ) (y) (y)。这 ( n ) (n) (n)对 ( x , y ) (x, y) (x,y)唯一确定了该多项式,即只有一个多项式能同时满足“代入这些 ( x ) (x) (x),得到的分别是这些 ( y ) (y) (y)”。
由多项式可以求出其点值表示,而由点值表示也可以求出多项式。
2.复数知识
复数相乘的规则:模长相乘,幅角相加。
3.单位根的性质
性质一: ω 2 n 2 k = ω n k \omega_{2n}^{2k} = \omega_{n}^{k} ω2n2k=ωnk
证明:它们对应的点/向量是相同的。
性质二: ω n k + n 2 = − ω n k \omega_{n}^{k+\frac{n}{2}}=-\omega_{n}^{k} ωnk+2n=−ωnk
证明:它们对应的点是关于原点对称的(对应的向量是等大反向的)。
1.什么是FFT
快速傅里叶变换(FFT)是一种能在 O ( n log n ) O(n\log n) O(nlogn)的时间内将一个多项式转换成它的点值表示的算法
2.为什么要使用FFT
FFT可以用来加速多项式乘法
假设有两个 n − 1 n-1 n−1次的多项式 A ( x ) A(x) A(x)和 B ( x ) B(x) B(x),我们的目标是把它们乘起来。
普通的多项式乘法是 O ( n 2 ) O(n^2) O(n2)的。
因为我们需要枚举 A ( x ) A(x) A(x)中的每一项,分别与 B ( x ) B(x) B(x)中的每一项相乘得到多项式 C ( x ) C(x) C(x)
但有趣的是,两个用点值表示的多项式相乘,复杂度是 O ( n ) O(n) O(n)的。
即:
C ( x i ) = A ( x i ) × B ( x i ) C(x_i)=A(x_i)\times B(x_i) C(xi)=A(xi)×B(xi),所以 O ( n ) O(n) O(n)枚举 x i x_i xi就行了
如果把两个多项式转换成点值表示,再相乘,在把新的点值表示转换成多项式不就可以 O ( n ) O(n) O(n)解决多项式乘法了?
但是,把多项式转换成点值表示的朴素算法是 O ( n 2 ) O(n^2) O(n2)。
所以大整数乘法的复杂度瓶颈就在于多项式转换成点值表示
这一步,只要完成这步,就能
O
(
n
)
O(n)
O(n)求答案了。
这时傅里叶就跑出来了…
3.离散傅里叶变换(快速傅里叶变换的朴素版)
傅里叶发明了一种办法:规定点值表示中的 n n n个 x x x为 n n n个模长为 1 1 1的复数。
C++的STL提供了复数模板
定义:complex<double> x;
运算:直接使用加减乘除。
傅里叶要用到的 n n n个复数,不是随机找的,而是——把单位圆(圆心为原点、 1 1 1为半径的圆) n n n等分,取这 n n n个点(或点表示的向量)所表示的虚数,即分别以这 n n n个点的横坐标为实部、纵坐标为虚部,所构成的虚数。
从点
(
1
,
0
)
(1,0)
(1,0)开始(该点显然是要去的点之一),逆时针将这
n
n
n个点从
0
0
0开始编号,第
k
k
k个点对应的虚数记作
ω
n
k
\omega_n^k
ωnk(根据复数相乘时模长相乘幅角相加可以看出,
ω
n
k
\omega_n^k
ωnk是
ω
n
1
\omega_n^1
ωn1的
k
k
k次方,所以
ω
n
1
\omega_n^1
ωn1被称为
n
n
n次单位根
)。
根据每个复数的幅角,可以计算出所对应的点/向量。 ω n k \omega_n^k ωnk对应的点/向量是 ( cos k n 2 π , sin k n 2 π ) (\cos\frac{k}{n}2\pi,\sin\frac{k}{n}2\pi) (cosnk2π,sinnk2π),也就是说这个复数是 cos k n 2 π + i sin k n 2 π \cos\frac{k}{n}2\pi+i\sin\frac{k}{n}2\pi cosnk2π+isinnk2π
傅里叶说:把 n n n个复数 ω n 0 , ω n 1 , ω n 2 , … , ω n n − 1 \omega_n^0,\omega_n^1,\omega_n^2,\dots,\omega_n^{n-1} ωn0,ωn1,ωn2,…,ωnn−1代入多项式,能得到一种特殊的点值表示,这种点值表示就叫离散傅里叶变换吧!
为什么要使用单位根作为 x x x代入
设 ( y 0 , y 1 , y 2 , … , y n − 1 ) (y_0,y_1,y_2,\dots,y_{n-1}) (y0,y1,y2,…,yn−1)为多项式 A ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n − 1 x n − 1 A(x)=a_0+a_1x+a_2x^2+\dots+a_{n-1}x^{n-1} A(x)=a0+a1x+a2x2+⋯+an−1xn−1的离散傅里叶变换。
再设一个多项式 B ( x ) = y 0 + y 1 x + y 2 x 2 + ⋯ + y n − 1 x n − 1 B(x)=y_0+y_1x+y_2x^2+\dots+y_{n-1}x^{n-1} B(x)=y0+y1x+y2x2+⋯+yn−1xn−1
现在我们把上面的
n
n
n个单位根的倒数,即
ω
n
0
,
ω
n
−
1
,
ω
n
−
2
,
…
,
ω
n
−
(
n
−
1
)
\omega_{n}^{0},\omega_{n}^{-1},\omega_{n}^{-2},\dots,\omega_{n}^{-(n-1)}
ωn0,ωn−1,ωn−2,…,ωn−(n−1)作为
x
x
x代入到
B
(
x
)
B(x)
B(x),得到了一个新的离散傅里叶变换
(
z
0
,
z
1
,
z
2
,
…
,
z
n
−
1
)
(z_0,z_1,z_2,\dots,z_{n-1})
(z0,z1,z2,…,zn−1)。
z k = ∑ i = 0 n − 1 y i ( ω n − k ) i = ∑ i = 0 n − 1 ( ∑ j = 0 n − 1 a j ( ω n i ) j ) ( ω n − k ) i = ∑ j = 0 n − 1 a j ( ∑ i = 0 n − 1 ( ω n j − k ) i ) \begin{aligned} z_k &=\sum_{i=0}^{n-1}y_i\left(\omega_n^{-k}\right)^i \\ &=\sum_{i=0}^{n-1}\left(\sum_{j=0}^{n-1}a_j\left(\omega_n^i\right)^j\right)\left(\omega_n^{-k}\right)^i \\ &=\sum_{j=0}^{n-1}a_j\left(\sum_{i=0}^{n-1}\left(\omega_n^{j-k}\right)^i\right) \end{aligned} zk=i=0∑n−1yi(ωn−k)i=i=0∑n−1(j=0∑n−1aj(ωni)j)(ωn−k)i=j=0∑n−1aj(i=0∑n−1(ωnj−k)i)
其中
∑
i
=
0
n
−
1
(
ω
n
j
−
k
)
\sum_{i=0}^{n-1}\left(\omega_{n}^{j-k}\right)
∑i=0n−1(ωnj−k)是可求的:当
j
−
k
=
0
j-k=0
j−k=0时,它等于
n
n
n。其他时候通过等比数列和可知它等于
(
ω
n
j
−
k
)
n
−
1
ω
n
j
−
k
−
1
=
(
ω
n
n
)
j
−
k
−
1
ω
n
j
−
k
−
1
=
1
j
−
k
−
1
ω
n
j
−
k
−
1
=
0
\frac{\left(\omega_n^{j-k}\right)^n-1}{\omega_n^{j-k}-1}=\frac{\left(\omega_n^n\right)^{j-k}-1}{\omega_n^{j-k}-1}=\frac{1^{j-k}-1}{\omega_n^{j-k}-1}=0
ωnj−k−1(ωnj−k)n−1=ωnj−k−1(ωnn)j−k−1=ωnj−k−11j−k−1=0
那么
z
k
z_k
zk就等于
n
a
k
na_k
nak,即:
a
i
=
z
i
n
a_i=\frac{z_i}{n}
ai=nzi
一个结论
把多项式 A ( x ) A(x) A(x)的离散傅里叶变换结果作为另一个多项式 B ( x ) B(x) B(x)的系数,取单位根的倒数即 ω n 0 , ω n − 1 , ω n − 2 , . . . , ω n − ( n − 1 ) \omega_{n}^{0},\omega_{n}^{-1},\omega_{n}^{-2},...,\omega_{n}^{-(n-1)} ωn0,ωn−1,ωn−2,...,ωn−(n−1)作为 x x x代入 B ( x ) B(x) B(x),得到的每个数再除以 n n n,得到的是 A ( x ) A(x) A(x)的各项系数。
这实现了傅里叶变换的逆变换——把点值表示转换成多项式系数表示,这就是离散傅里叶变换神奇的性质。
4.快速傅里叶变换
虽然傅里叶发明了神奇的变换,能把多项式转换成点值表示又转换回来,但是……它仍然是暴力代入的做法,复杂度仍是 O ( n 2 ) O(n^2) O(n2)
于是,快速傅里叶变换应运而生。它是一种分治的傅里叶变换。
快速傅里叶变换的数学证明
同样的,设 A ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n − 1 x n − 1 A(x)=a_0+a_1x+a_2x^2+\dots+a_{n-1}x^{n-1} A(x)=a0+a1x+a2x2+⋯+an−1xn−1,现在为了求离散傅里叶变换,要把一个 x = ω n k x=\omega^k_n x=ωnk代入。
考虑将 A ( x ) A(x) A(x)的每一项按照下标奇偶分成两部分:
A ( x ) = ( a o + a 2 x 2 + ⋯ + a n − 2 x n − 2 ) + ( a 1 x + a 3 x 3 + ⋯ + a n − 1 x n − 1 ) A(x)=\left(a_o+a_2x^2+\dots+a_{n-2}x^{n-2}\right)+\left(a_1x+a_3x^3+\dots+a_{n-1}x^{n-1}\right) A(x)=(ao+a2x2+⋯+an−2xn−2)+(a1x+a3x3+⋯+an−1xn−1)
设两个多项式:
A
1
(
x
)
=
a
0
+
a
2
x
+
.
.
.
+
a
n
−
2
x
n
2
−
1
A_1(x)=a_0+a_2x+...+a_{n-2}x^{\frac{n}{2}-1}
A1(x)=a0+a2x+...+an−2x2n−1
A 2 ( x ) = a 1 + a 3 x + . . . + a n − 1 x n 2 − 1 A_2(x)=a_1+a_3x+...+a_{n-1}x^{\frac{n}{2}-1} A2(x)=a1+a3x+...+an−1x2n−1
则:
A
(
x
)
=
A
1
(
x
2
)
+
x
A
2
(
x
2
)
A(x)=A_1(x^2)+xA_2(x^2)
A(x)=A1(x2)+xA2(x2)
假设
k
<
n
2
k<\frac{n}{2}
k<2n,现在将
x
=
ω
n
k
x=\omega^k_n
x=ωnk代入:
A
(
ω
n
k
)
=
A
1
(
ω
n
2
k
)
+
ω
n
k
A
2
(
ω
n
2
k
)
=
A
1
(
ω
n
2
k
)
+
ω
n
k
A
2
(
ω
n
2
k
)
\begin{aligned}A\left(\omega_n^k\right)&=A_1\left(\omega_n^{2k}\right)+\omega_n^kA_2\left(\omega_n^{2k}\right)\\&=A_1\left(\omega_{\frac{n}{2}}^{k}\right)+\omega_n^kA_2\left(\omega_{\frac{n}{2}}^{k}\right)\end{aligned}
A(ωnk)=A1(ωn2k)+ωnkA2(ωn2k)=A1(ω2nk)+ωnkA2(ω2nk)
那么对于
A
(
ω
n
k
+
n
2
)
A\left(\omega^{k+\frac{n}{2}}_n\right)
A(ωnk+2n)
A
(
ω
n
k
+
n
2
)
=
A
1
(
ω
n
2
k
+
n
)
+
ω
n
k
+
n
2
A
2
(
ω
n
2
k
+
n
)
=
A
1
(
ω
n
2
k
×
ω
n
n
)
+
ω
n
k
+
n
2
A
2
(
ω
n
2
k
×
ω
n
n
)
=
A
1
(
ω
n
2
k
)
−
ω
n
k
A
2
(
ω
n
2
k
)
\begin{aligned}A\left(\omega_n^{k+\frac{n}{2}}\right)&=A_1\left(\omega_n^{2k+n}\right)+\omega_n^{k+\frac{n}{2}}A_2\left(\omega_n^{2k+n}\right)\\&=A_1\left(\omega_{\frac{n}{2}}^{k}\times\omega_n^n\right)+\omega_n^{k+\frac{n}{2}}A_2\left(\omega_{\frac{n}{2}}^{k}\times\omega_n^n\right)\\&=A_1\left(\omega_{\frac{n}{2}}^{k}\right)-\omega_n^kA_2\left(\omega_{\frac{n}{2}}^{k}\right)\end{aligned}
A(ωnk+2n)=A1(ωn2k+n)+ωnk+2nA2(ωn2k+n)=A1(ω2nk×ωnn)+ωnk+2nA2(ω2nk×ωnn)=A1(ω2nk)−ωnkA2(ω2nk)
所以,如果我们能知道两个多项式
A
1
(
x
)
A_1(x)
A1(x)和
A
2
(
x
)
A_2(x)
A2(x)分别在
(
ω
n
2
0
,
ω
n
2
1
,
ω
n
2
2
,
…
,
ω
n
2
n
2
−
1
)
\left(\omega_{\frac{n}{2}}^{0},\omega_{\frac{n}{2}}^{1},\omega_{\frac{n}{2}}^{2},\dots,\omega_{\frac{n}{2}}^{\frac{n}{2}-1}\right)
(ω2n0,ω2n1,ω2n2,…,ω2n2n−1)的点值表示,就可以
O
(
n
)
O(n)
O(n)求出
A
(
x
)
A(x)
A(x)在
ω
n
0
,
ω
n
1
,
ω
n
2
,
⋯
ω
n
n
−
1
\omega_n^0,\omega_n^1,\omega_n^2,\dotsm\omega^{n-1}_{n}
ωn0,ωn1,ωn2,⋯ωnn−1处的点值表示了。而
A
1
(
x
)
A_1(x)
A1(x)和
A
2
(
x
)
A_2(x)
A2(x)都是规模缩小了一半的子问题。分治边界是
n
=
1
n=1
n=1,此时直接return
。
快速傅里叶变换的实现
typedef complex<double> cp;
const double PI=acos(-1);
cp omega(int n,int k)
{
return cp(cos(2*PI*k/n),sin(2*PI*k/n));
}
void fft(cp *a,int n,bool inv)
{
if(n==1)
{
return ;
}
static cp buf[N];
int m=n/2;
for(int i=0;i<m;i++)//将每一项按照奇偶分为两组
{
buf[i]=a[2*i];
buf[i+m]=a[2*i+1];
}
for(int i=0;i<n;i++)
{
a[i]=buf[i];
}
fft(a,m,inv); //递归处理两个子问题
fft(a+m,m,inv);
for(int i=0;i<m;i++)//枚举x,计算A(x)
{
cp x=omega(n,i);
if(inv)
{
x=conj(x);//conj是一个自带的求共轭复数的函数,精度较高。当复数模为1时,共轭复数等于倒数
}
buf[i]=a[i]+x*a[i+m];//根据之前推出的结论计算
buf[i+m]=a[i]-x*a[i+m];
}
for(int i=0;i<n;i++)
{
a[i]=buf[i];
}
}
inv
表示这次用的单位根是否要取倒数。