快速傅里叶变换(FFT)略解

前言

如果我们能用一种时间上比 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 =31 .
类似地, ∀ N ∈ Z − \forall N\in \Z_- NZ 我们都可以用类似的方法得到 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,bR 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辐角

单位根及其性质

  1. ∀ k , n ∈ N ∗ \forall k,n\in\N^* k,nN ω 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θ)×isinθ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,n1]。那么我们一定有 ω 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,n1]
    特别地,我们有 ω n n = ω n 0 \omega_n^n=\omega_n^0 ωnn=ωn0。它们叫做 n n n单位根

  2. ω n 0 = ω n n = 1 \omega_n^0=\omega_n^n=1 ωn0=ωnn=1

  3. ω n 0 , ω n 1 , . . . , ω n n − 1 \omega_n^0,\omega_n^1,...,\omega_n^{n-1} ωn0,ωn1,...,ωnn1 互不相同;

  4. ∀ k , n ∈ N ∗ \forall k,n\in\N^* k,nN ω 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.

  1. ∀ k , n ∈ N ∗ \forall k,n\in\N^* k,nN ω 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.

  1. ∀ k , n ∈ N ∗ \forall k,n\in\N^* k,nN ∑ 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=0n1(ω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=0n1(ω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=1n(ω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} (ωnk1)×SS=(ωnk)n1=ωnk1(ωnn)k1=ωnk111=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=0n11=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=0n1aixi=(a0+a2x+...+an2xn2)+(a1x+a3x3+...+an1xn1)
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+...+an2x2n1=a1+a3x+...+an1x2n1
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}\} {ωni} 的值。
则有 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=0n1yi(ωnk)i=i=0n1(j=0n1aj(ωnk)j)(ωnk)i=i=0n1aj×j=0n1(ωnjk)i
根据 单位根的性质6 ,有且仅有一个 j ∈ [ 0 , n − 1 ] j\in[0,n-1] j[0,n1] 使得 j = k j=k j=k
∴ ∀ j \therefore \forall j j 有且仅有一个 ( ω n j − k ) i = n {(\omega_n^{j-k})^i}=n (ωnjk)i=n有且仅有 n − 1 n-1 n1 ( ω n j − k ) i = 0 {(\omega_n^{j-k})^i}=0 (ωnjk)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=0n1(ωnjk)ickak=n,=i=0n1aj×j=0n1(ωnjk)i=ak×n,=nck.
换句话说,经过快速傅里叶逆变换后的数组 { c i } \{c_i\} {ci},除以 n n n 后就得到了结果 { a i } \{a_i\} {ai}。(这里的 n n n 是指 c c c 数组的长度)

小结

使用快速傅里叶变换(FFT)计算两个函数之积的步骤如下:

  1. 分别对两个函数进行快速傅里叶变换;
  2. 将两组结果相乘;
  3. 对结果进行快速傅里叶逆变换,并将结果除以 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 函数是被递归调用的,所以会造成爆栈。

原序列12345678
快速傅里叶变换后序列15372648
原下标的二进制000100010110001101011111
新下标的二进制000001010011100101110111

不难发现,快速傅里叶变换后的序列中,每个数的新下标的二进制,在数值上等于原下标二进制的反转。

设原下标为 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&amp;\ &amp;0&amp;\ &amp;1&amp;\ &amp;1&amp;\ &amp;1&amp;\ &amp;0&amp;\ &amp;0&amp;|&amp;0\end{array} 1 0 1 1 1 0 00
我们把这个数分为两部分,第一部分是前 l − 1 l-1 l1 位,第二部分是最后一位。不难得到,它的翻转就是 在第二部分后面接上第一部分的翻转形成的数

	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,m106,和 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));
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值