FFT(快速傅立叶变换)
向量基本知识
- 向量加法
AB=r1eiθ1=r1(cosθ1+isinθ1)AC=r2eiθ2=r2(cosθ2+isinθ2)AB+AC=r1eiθ1+r2eiθ2=r1(cosθ1+isinθ1)+r2(cosθ2+isinθ2)AB+AC=r1eiθ1+r2eiθ2=(r1cosθ1+r2cosθ2)+i(r1sinθ1+r2sinθ2) AB=r_1e^{i\theta_1}=r_1(\cos{\theta_1}+i\sin{\theta_1})\\ AC=r_2e^{i\theta_2}=r_2(\cos{\theta_2}+i\sin{\theta_2})\\ \\ AB+AC=r_1e^{i\theta_1}+r_2e^{i\theta_2}=r_1(\cos{\theta_1}+i\sin{\theta_1})+r_2(\cos{\theta_2}+i\sin{\theta_2})\\ AB+AC=r_1e^{i\theta_1}+r_2e^{i\theta_2}=(r_1\cos{\theta_1}+r_2\cos{\theta_2})+i(r_1\sin{\theta_1}+r_2\sin{\theta_2})\\ AB=r1eiθ1=r1(cosθ1+isinθ1)AC=r2eiθ2=r2(cosθ2+isinθ2)AB+AC=r1eiθ1+r2eiθ2=r1(cosθ1+isinθ1)+r2(cosθ2+isinθ2)AB+AC=r1eiθ1+r2eiθ2=(r1cosθ1+r2cosθ2)+i(r1sinθ1+r2sinθ2)
-
向量乘法
向量乘法可以简单看成向量的旋转和放缩
如图所示最后AB×AC=ADAB\times AC=ADAB×AC=AD,iii可以想象成逆时针旋转90∘90^{\circ}90∘,所以总结出来 r1eiθ1×r2eiθ2=r1r2ei(θ1+θ2)r_1e^{i\theta_1}\times r_2e^{i\theta_2}=r_1r_2e^{i(\theta_1+\theta_2)}r1eiθ1×r2eiθ2=r1r2ei(θ1+θ2),
复数的单位根ωn\omega_nωn
复数的单位根是指满足方程ωn=1\omega^n=1ωn=1的复数 ω\omegaω,其中 nnn是一个正整数。这些根称为 nnn 次单位根,它们在复平面上均匀分布在单位圆上,形成一个正nnn 边形的顶点。
满足的性质:
- ωn=1\omega^n=1ωn=1
- ωn+k=ωk\omega^{n+k}=\omega^{k}ωn+k=ωk
- ωnk=ω2n2k\omega_n^k=\omega_{2n}^{2k}ωnk=ω2n2k
- ωnn−k=ωnk‾\omega_n^{n-k} = \overline{\omega_n^k}ωnn−k=ωnk 共轭复数
- ωnk+n2=−ωnk\omega_n^{k+\frac{n}{2}}=-\omega_{n}^{k}ωnk+2n=−ωnk ,其实就是逆时针转半圆
- ωnk+ωnn−k=Re(ωnk)\omega_n^k+\omega_n^{n-k}=Re(\omega_n^k)ωnk+ωnn−k=Re(ωnk)
- ωnk−ωnn−k=Im(ωnk)\omega_n^k-\omega_n^{n-k}=Im(\omega_n^k)ωnk−ωnn−k=Im(ωnk)
FFT 整体思路
FFT其实就是通过分治策略利用单位根的对称性和周期性加速多项式乘法
-
多项式插值转换:将多项式 (f(x)=∑k=0n−1akxkf(x) = \sum_{k=0}^{n-1} a_k x^kf(x)=∑k=0n−1akxk) 在单位根 (ωn0,ωn1,…,ωnn−1\omega_n^0, \omega_n^1, \dots, \omega_n^{n-1}ωn0,ωn1,…,ωnn−1) 处求值DFT。
-
分治步骤
- 偶数项和奇数项分别递归计算:
f(x)=feven(x2)+x⋅fodd(x2) f(x) = f_{\text{even}}(x^2) + x \cdot f_{\text{odd}}(x^2) f(x)=feven(x2)+x⋅fodd(x2) - 利用 (ωnk+n/2=−ωnk\omega_n^{k + n/2} = -\omega_n^kωnk+n/2=−ωnk) 合并结果(蝴蝶变换):
{f(ωnk)=feven(ωn/2k)+ωnk⋅fodd(ωn/2k)f(ωnk+n/2)=feven(ωn/2k)−ωnk⋅fodd(ωn/2k) \begin{cases} f(\omega_n^k) = f_{\text{even}}(\omega_{n/2}^k) + \omega_n^k \cdot f_{\text{odd}}(\omega_{n/2}^k) \\ f(\omega_n^{k + n/2}) = f_{\text{even}}(\omega_{n/2}^k) - \omega_n^k \cdot f_{\text{odd}}(\omega_{n/2}^k) \end{cases} {f(ωnk)=feven(ωn/2k)+ωnk⋅fodd(ωn/2k)f(ωnk+n/2)=feven(ωn/2k)−ωnk⋅fodd(ωn/2k)
- 偶数项和奇数项分别递归计算:
多项式乘法
f(x)∗g(x)=a0b0+∑i=01aib1−ix1+⋯+∑i=0n+m−2aibn+m−2j,这显然是个O((n+m)2)的算法
f(x)*g(x)=a_0b_0+\sum_{i=0}^1a_ib_{1-i}x^1+\cdots+\sum_{i=0}^{n+m-2}a_ib_{n+m-2j} , 这显然是个O((n+m)^2)的算法
f(x)∗g(x)=a0b0+i=0∑1aib1−ix1+⋯+i=0∑n+m−2aibn+m−2j,这显然是个O((n+m)2)的算法
DFT和IDFT
我们先来看看转化成频域之后会发生什么,将多项式 (f(x)=∑k=0n−1akxkf(x) = \sum_{k=0}^{n-1} a_k x^kf(x)=∑k=0n−1akxk) 在单位根 ( ωn0,ωn1,…,ωnn−1\omega_n^0, \omega_n^1, \dots, \omega_n^{n-1}ωn0,ωn1,…,ωnn−1) 处求值
f(x)=∑k=0n−1akxk设置dk=∑i=0n−1aiωnki,那么ak=∑i=0n−1dkwn−ki
f(x) = \sum_{k=0}^{n-1} a_k x^k \\
设置d_k=\sum_{i=0}^{n-1}a_i\omega_{n}^{ki},那么a_k=\sum_{i=0}^{n-1}d_k{w_n^{-ki}}
f(x)=k=0∑n−1akxk设置dk=i=0∑n−1aiωnki,那么ak=i=0∑n−1dkwn−ki
这实际上是线性变换,映射到新的基上,dkd_kdk其实是基于nnn个基{ωn0,⋯ ,wn(n−1)k}\{\omega_n^0,\cdots,w_n^{(n-1)k}\}{ωn0,⋯,wn(n−1)k}表示的一个向量{a0,⋯ ,an−1}\{a_0,\cdots,a_{n-1}\}{a0,⋯,an−1},
那么一共有nnn 种基
W=(ωn0ωn0⋯ωn0ωn0ωn1×1⋯ωn(n−1)×1⋮⋮⋱⋮ωn0ωn1×(n−1)⋯ωn(n−1)(n−1))
W = \begin{pmatrix}
\omega_n^0 & \omega_n^0 & \cdots & \omega_n^0 \\
\omega_n^0 & \omega_n^{1 \times 1} & \cdots & \omega_n^{(n-1)\times 1} \\
\vdots & \vdots & \ddots & \vdots \\
\omega_n^0 & \omega_n^{1 \times (n-1)} & \cdots & \omega_n^{(n-1)(n-1)}
\end{pmatrix}
W=ωn0ωn0⋮ωn0ωn0ωn1×1⋮ωn1×(n−1)⋯⋯⋱⋯ωn0ωn(n−1)×1⋮ωn(n−1)(n−1)
我们要证明的是:
对于 k≠lk\ne lk=l,这两个向量是正交的:
uk⃗=[1,ωnk,ωn2k,⋯ ,ωn(n−1)k]ul⃗=[1,ωnl,ωn2l,⋯ ,ωn(n−1)l]
\vec{u_k} =\left[1, \omega_n^k, \omega_n^{2k}, \cdots, \omega_n^{(n-1)k}\right] \\ \vec{u_l} = \left[1, \omega_n^l, \omega_n^{2l}, \cdots, \omega_n^{(n-1)l}\right]
uk=[1,ωnk,ωn2k,⋯,ωn(n−1)k]ul=[1,ωnl,ωn2l,⋯,ωn(n−1)l]
它们的内积为 0:
⟨uk⃗,ul⃗⟩=∑m=0n−1ωnkm⋅ωnlm‾=∑m=0n−1ωnkm⋅ωn−lm=∑m=0n−1ωn(k−l)m=ωnn−1ωn−1=0
\begin{align*}
\langle\vec{u_k},\vec{u_l}\rangle
& =\sum_{m=0}^{n-1} \omega_n^{km} \cdot \overline{\omega_n^{lm}}\\
&= \sum_{m=0}^{n-1} \omega_n^{km} \cdot \omega_n^{-lm}\\
&= \sum_{m=0}^{n-1} \omega_n^{(k-l)m}\\
&=\frac{\omega_n^n-1}{\omega_n-1}=0
\end{align*}
⟨uk,ul⟩=m=0∑n−1ωnkm⋅ωnlm=m=0∑n−1ωnkm⋅ωn−lm=m=0∑n−1ωn(k−l)m=ωn−1ωnn−1=0
当 k=lk=lk=l 时,显然
⟨uk⃗,uk⃗⟩=∑m=0n−1ωn0=n
\langle\vec{u_k},\vec{u_k}\rangle= \sum_{m=0}^{n-1} \omega_n^{0}=n
⟨uk,uk⟩=m=0∑n−1ωn0=n
神奇,因此
WWH=nIn
WW^{H}=nI_n
WWH=nIn
因此DFT就是构造了这样一组正交基
(d0d1⋮dn−1)=(ωn0⋅0ωn0⋅1⋯ωn0⋅(n−1)ωn1⋅0ωn1⋅1⋯ωn1⋅(n−1)⋮⋮⋱⋮ωn(n−1)⋅0ωn(n−1)⋅1⋯ωn(n−1)⋅(n−1))(a0a1⋮an−1)
\begin{pmatrix}
d_0 \\
d_1\\
\vdots\\
d_{n-1}
\end{pmatrix}=
\begin{pmatrix}
\omega_n^{0\cdot0} & \omega_n^{0\cdot1} & \cdots & \omega_n^{0\cdot(n-1)} \\
\omega_n^{1\cdot0} & \omega_n^{1\cdot1} & \cdots & \omega_n^{1\cdot(n-1)} \\
\vdots & \vdots & \ddots & \vdots \\
\omega_n^{(n-1)\cdot0} & \omega_n^{(n-1)\cdot1} & \cdots & \omega_n^{(n-1)\cdot(n-1)}
\end{pmatrix}
\begin{pmatrix}
a_0 \\
a_1\\
\vdots \\
a_{n-1}
\end{pmatrix}
d0d1⋮dn−1=ωn0⋅0ωn1⋅0⋮ωn(n−1)⋅0ωn0⋅1ωn1⋅1⋮ωn(n−1)⋅1⋯⋯⋱⋯ωn0⋅(n−1)ωn1⋅(n−1)⋮ωn(n−1)⋅(n−1)a0a1⋮an−1
ωn=e2πin\omega_n=e^{\frac{2πi}{n}}ωn=en2πi,是 DFT 的单位根,上式就是 DFT 的矩阵乘法形式,d⃗=Wa⃗\vec{d} = W \vec{a}d=Wa,因此na⃗=WHd⃗n\vec{a} = W^H \vec{d}na=WHd
用WHW^HWH求解a⃗\vec{a}a的过程就是DFT的逆变换IDFT
DFT本质上是将输入信号在复平面上进行周期性旋转。不同的旋转角度代表不同的频率成分,这些频率成分合成后就形成了原始信号在频域中的表示。
在IDFT中,频域信号不仅要按相反的方向旋转回去,还需要通过 1n\frac{1}{n}n1 进行缩放,这样才能正确地恢复原始信号。每个频率成分在时间域的表示受到频域中其幅度的影响。
题外话
有没有发现这个WWW与范德蒙矩阵很相似。
范德蒙矩阵的一般形式为:
V=(1x0x02⋯x0n−11x1x12⋯x1n−1⋮⋮⋮⋱⋮1xn−1xn−12⋯xn−1n−1)
V = \begin{pmatrix}
1 & x_0 & x_0^2 & \cdots & x_0^{n-1} \\
1 & x_1 & x_1^2 & \cdots & x_1^{n-1} \\
\vdots & \vdots & \vdots & \ddots & \vdots \\
1 & x_{n-1} & x_{n-1}^2 & \cdots & x_{n-1}^{n-1}
\end{pmatrix}
V=11⋮1x0x1⋮xn−1x02x12⋮xn−12⋯⋯⋱⋯x0n−1x1n−1⋮xn−1n−1
范德蒙矩阵可以用来做多项式插值。
多项式乘法
原始时域卷积表达式
多项式乘法对应的是离散卷积:
f(x)⋅g(x)=∑k=0n+m−2(∑j=0kajbk−j)xk
f(x) \cdot g(x) = \sum_{k=0}^{n+m-2} \left( \sum_{j=0}^{k} a_j b_{k-j} \right) x^k
f(x)⋅g(x)=k=0∑n+m−2(j=0∑kajbk−j)xk
也就是:
ck=∑j=0kajbk−j
c_k = \sum_{j=0}^{k} a_j b_{k-j}
ck=j=0∑kajbk−j
转化到频域
DFT(c)k=∑i=0n−1ciωnki=∑i=0n−1∑j=0iajbi−jwnik=∑i=0n−1∑j=0i(ajwnjk)(bi−jwn(i−j)k)=DFT(a)k∗DFT(b)k \begin{align*} DFT(c)_k &= \sum_{i=0}^{n-1}c_i\omega_{n}^{ki}\\ &= \sum_{i=0}^{n-1} \sum_{j=0}^{i} a_j b_{i-j} w_n^{ik}\\ &= \sum_{i=0}^{n-1} \sum_{j=0}^{i} (a_jw_n^{jk})( b_{i-j} w_n^{(i-j)k})\\ &=DFT(a)_k*DFT(b)_k \end{align*} DFT(c)k=i=0∑n−1ciωnki=i=0∑n−1j=0∑iajbi−jwnik=i=0∑n−1j=0∑i(ajwnjk)(bi−jwn(i−j)k)=DFT(a)k∗DFT(b)k
因此多项式卷积的频域就是多项式频域点积的积。
实际上就是将全局纠缠的时域操作解耦为局部独立的频域操作
FFT
说了那么多,最后该说FFT了,FFT其实就是利用分治进行DFT操作。
FFT其实就是通过分治策略利用单位根的对称性和周期性加速多项式乘法
-
多项式插值转换:将多项式 (f(x)=∑k=0n−1akxkf(x) = \sum_{k=0}^{n-1} a_k x^kf(x)=∑k=0n−1akxk) 在单位根 (ωn0,ωn1,…,ωnn−1\omega_n^0, \omega_n^1, \dots, \omega_n^{n-1}ωn0,ωn1,…,ωnn−1) 处求值DFT。
-
分治步骤
-
偶数项和奇数项分别递归计算:
f(x)=feven(x2)+x⋅fodd(x2) f(x) = f_{\text{even}}(x^2) + x \cdot f_{\text{odd}}(x^2) f(x)=feven(x2)+x⋅fodd(x2) -
利用 (ωnk+n/2=−ωnk\omega_n^{k + n/2} = -\omega_n^kωnk+n/2=−ωnk) 合并结果(蝴蝶变换):
{bk=f(ωnk)=feven(ωn/2k)+ωnk⋅fodd(ωn/2k)bk+n2=f(ωnk+n/2)=feven(ωn/2k)−ωnk⋅fodd(ωn/2k) \begin{cases} b_k=f(\omega_n^k) = f_{\text{even}}(\omega_{n/2}^k) + \omega_n^k \cdot f_{\text{odd}}(\omega_{n/2}^k) \\ b_{k+\frac{n}{2}}=f(\omega_n^{k + n/2}) = f_{\text{even}}(\omega_{n/2}^k) - \omega_n^k \cdot f_{\text{odd}}(\omega_{n/2}^k) \end{cases} {bk=f(ωnk)=feven(ωn/2k)+ωnk⋅fodd(ωn/2k)bk+2n=f(ωnk+n/2)=feven(ωn/2k)−ωnk⋅fodd(ωn/2k)
-
奇偶分组
由于不断要奇偶分组,我们需要把每个数字移动到该有的位置,我们可以进行一个模拟,假设数组的长度为2p2^p2p
位置iii的数应该在哪:
最终的位置pos=0pos=0pos=0,很明显可以进行一个递归,如果iii是奇数,p=p−1,pos=pos+2p,i=i/2p=p-1,pos=pos+2^p,i=i/2p=p−1,pos=pos+2p,i=i/2 这明显是对二进制位进行一个反转。
即假设数组长度为888, 那么位置4=0100,pos=00104=0100,pos=00104=0100,pos=0010,位置3=0011,pos=11003=0011,pos=11003=0011,pos=1100,同理,pospospos位置上的数来自于对pospospos二进制的反转。
可以设计一个递推对于一个数字iii的反转来自于最低位和其他高位即
reverse[i]=reverse[i>>1]>>1+(i&1)<<(p-1)
分治
在进行奇偶分组后,利用变换计算bk+n2b_{k+\frac{n}{2}}bk+2n 可以利用分治解决了,为了更清楚的看分治,把式子改写下
若l<k<mid,利用对称性和周期性Bl,r[k]=fl,mid(ωmid−lk)+ωnk⋅fmid,r(ωr−midk+r−mid)Bl,r[k]=Bl,mid[k]+ωnk⋅Bmid,r[k+r−mid]Bl,r[k+r−mid]=Bl,mid[k]−ωnk⋅Bmid,r[k+r−mid]
若l<k<mid,利用对称性和周期性\\
\begin{align*}
B_{l,r}[k]&= f_{\text{l,mid}}(\omega_{mid-l}^k) + \omega_n^k \cdot f_{\text{mid,r}}(\omega_{r-mid}^{k+r-mid}) \\
B_{l,r}[k]&= B_{l,mid}[k] + \omega_n^k \cdot B_{mid,r}[k+r-mid] \\
B_{l,r}[k+r-mid]&= B_{l,mid}[k] - \omega_n^k \cdot B_{mid,r}[k+r-mid] \\
\end{align*}
若l<k<mid,利用对称性和周期性Bl,r[k]Bl,r[k]Bl,r[k+r−mid]=fl,mid(ωmid−lk)+ωnk⋅fmid,r(ωr−midk+r−mid)=Bl,mid[k]+ωnk⋅Bmid,r[k+r−mid]=Bl,mid[k]−ωnk⋅Bmid,r[k+r−mid]
代码
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
const double PI=acos(-1.0);
struct Complex{
double x,y;
Complex():x(0),y(0){};
Complex(double _x,double _y):x(_x),y(_y){};
Complex operator-(const Complex &b){return Complex(x-b.x,y-b.y); }
Complex operator+(const Complex &b){return Complex(x+b.x,y+b.y);}
Complex operator*(const Complex &b){return Complex(x*b.x-y*b.y,x*b.y+y*b.x);}
Complex operator/(const double &b){return Complex(x/b,y/b);}
};
vector<int> re,omega;
int len=1;
void init(int n){
while(len<n) len<<=1;re.resize(len,0);
for(int i=1,t=(len>>1);i<len;i++) re[i]=((re[i>>1]>>1)|((i&1)?t:0));
}
void move(vector<Complex> &f){for(int i=0;i<len;i++)if(i<re[i]) swap(f[i],f[re[i]]);}
void FFT(vector<Complex> &f,double flag){
move(f);
double num=flag?PI:-PI;
for(int l=2,preL=1;l<=len;preL=l,l<<=1,num/=2){
Complex wn(cos(num),sin(num));
for(int j=0;j<len;j+=l){
Complex w(1,0);
for(int k=j;k<j+preL;k++){
Complex u=f[k],v=f[k+preL]*w;
f[k]=u+v,f[k+preL]=u-v;
w=w*wn;
}
}
}
if(flag) for(int i=0;i<len;i++) f[i]=f[i]/len;
}
vector<Complex>a,b;
int main(){
int n,m;cin>>n>>m;n++;m++;
init(n+m);
a.resize(len,Complex(0,0));
b.resize(len,Complex(0,0));
for(int i=0;i<n;i++) cin>>a[i].x;
for(int i=0;i<m;i++) cin>>b[i].x;
FFT(a,0);
FFT(b,0);
for(int i=0;i<len;i++) a[i]=a[i]*b[i];
FFT(a,1);
for(int i=0;i<n+m-1;i++) cout<<(int)(a[i].x+0.5)<<" ";cout<<endl;
}
NTT(快速数论变换)-代更
- 上面我们说利用复数单位根来进行加速,复数的单位根是指满足方程$ \omega^n=1$的复数 ω\omegaω,那么NTT就是利用原根来代替复数单位根。
原根
原根的定义
定义:设正整数n≥2n\ge 2n≥2,整数ggg与nnn互质(即gcd(g,n)=1gcd(g,n)=1gcd(g,n)=1)。如果ggg模nnn的阶等于ϕ(n)\phi(n)ϕ(n),则称ggg是模nnn的一个原根。
其中:
- ϕ(n)\phi(n)ϕ(n)是欧拉函数,表示小于nnn且与nnn互质的正整数的个数
- ggg模nnn的阶是最小的正整数ddd,使得gd≡ 1(modn)g^d \equiv \ 1\pmod ngd≡ 1(modn)