我就是不会FFT怎么了?!

写这篇东西的原因

我就是不会FFT怎么了!我只会念法法塔

复数?

大佬嫌我不懂复数,觉得我太LJ了,于是便Diss我。
复数:形如 a + b i , ( a , b ∈ R ) a+bi,(a,b∈R) a+bi,(a,bR)的数。
n次单位复数根: ( w n ) n = 1 (w_n)^n=1 (wn)n=1
n次单位复数根恰好有n个,即: w = e 2 π i ∗ k n , ( k ∈ [ 0 , n ) ) w=e^{\frac{2\pi i*k}{n}},(k∈[0,n)) w=en2πik,(k[0,n))
其中,i为虚数单位。 e u i = c o s ( u ) + i ∗ s i n ( u ) e^{ui}=cos(u)+i*sin(u) eui=cos(u)+isin(u)代表单位圆上的一点,原点与其所在射线 与 x 与x x的正半轴成角 u u u
所以n次单位复数根可看作是一个指针转一圈,又回到(1,0)。
几个最重要的原理:
1.消去引理
w d n d k = w n k w^{dk}_{dn}=w^k_n wdndk=wnk
2.折半引理
如果n为偶数且n>0,则n次单位复数跟的平方 的集合就是n/2次单位复数根的集合。
用消去引理证明。
3.求和引理
∀ n ≥ 1 , k ≥ 0 , n ∤ k \forall n≥1,k≥0,n\nmid k n1,k0nk,有 Σ j = 0 n − 1 ( w n k ) j = 0 \Sigma_{j=0}^{n-1}(w_n^k)^j=0 Σj=0n1(wnk)j=0
等比数列求和。
Σ j = 0 n − 1 ( w n k ) j = ( w n k ) n − 1 w n k − 1 = ( w n n ) k − 1 w n k − 1 = ( 1 ) k − 1 w n k − 1 = 0 \Sigma_{j=0}^{n-1}(w_n^k)^j=\frac{(w_n^k)^n-1}{w_n^k-1}=\frac{(w_n^n)^k-1}{w_n^k-1}=\frac{(1)^k-1}{w_n^k-1}=0 Σj=0n1(wnk)j=wnk1(wnk)n1=wnk1(wnn)k1=wnk1(1)k1=0
分母显然不为0,因为 n ∤ k n\nmid k nk.

一些基本概念

1.次数界:多项式的最高项的次数+1.
2.系数表示: A ( x ) = Σ i = 0 n − 1 a i x i A(x)=\Sigma_{i=0}^{n-1}a_ix^i A(x)=Σi=0n1aixi
3*.点值和插值:
点值表示:由点值对组成的集合。 S = ( x 1 , A ( x 1 ) ) , ( x 2 , A ( x 2 ) ) , . . , ( x n , A ( x n ) ) S={(x_1,A(x_1)),(x_2,A(x_2)),..,(x_n,A(x_n))} S=(x1,A(x1)),(x2,A(x2)),..,(xn,A(xn))
一般地,一个次数界为n的多项式的点值表示, ∣ S ∣ ≥ n |S|≥n Sn
其中,每个x必须互不相同。
点值运算:根据多项式A(x)的系数表示,得到多项式的点值表示的过程。
插值运算:点值运算的逆运算。假设现有一个n个点值对的点值表达,那么我们可以确定唯一的一个次数界为n的多项式。
为什么?考虑高斯消元。不就是解一个一次方程组吗?
最简单的例子:两点确定一条直线,三点确定一条抛物线。

!!!4.FFT原理

考虑以下两个东西。
1.点值表示的多项式乘法。2.对乘法结果进行插值运算。
1.点值表示的多项式乘法
对于两个多项式A,B,它们是被两个相同个点值对表示的唯一多项式
确定一组x,则有
A : ( x 0 , y 0 ) , ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x n − 1 , y n − 1 ) A:{(x_0,y_0),(x_1,y_1),(x_2,y_2),...,(x_{n-1},y_{n-1})} A:(x0,y0),(x1,y1),(x2,y2),...,(xn1,yn1)
B : ( x 0 , y 0 ′ ) , ( x 1 , y 1 ′ ) , ( x 2 , y 2 ′ ) , . . . , ( x n − 1 , y n − 1 ′ ) B:{(x_0,y'_0),(x_1,y'_1),(x_2,y'_2),...,(x_{n-1},y'_{n-1})} B:(x0,y0),(x1,y1),(x2,y2),...,(xn1,yn1)
C = A ∗ B C=A*B C=AB,则可得到C的点值表示
C : ( x 0 , y 0 ∗ y 0 ′ ) , ( x 1 , y 1 ∗ y 1 ′ ) , ( x 2 , y 2 ∗ y 2 ′ ) , . . . , ( x n − 1 , y 3 ∗ y n − 1 ′ ) C:{(x_0,y_0*y'_0),(x_1,y_1*y'_1),(x_2,y_2*y'_2),...,(x_{n-1},y_3*y'_{n-1})} C:(x0,y0y0),(x1,y1y1),(x2,y2y2),...,(xn1,y3yn1)
显然,要用2n对来表示A,B,不然计算不了C。
显然时间复杂度: O ( n ) O(n) O(n)
2.对乘法结果进行插值运算
确定多项式C的系数。

进入正题

DFT

DFT:离散傅里叶变换(Discrete Fourier Transform)
进行点值操作!
有大佬将整个傅里叶变换从头到尾看一遍,由于我是菜鸡,看不懂。
目标:计算一个次数界为n的多项式 A ( x ) = Σ j = 0 n − 1 a j x j A(x)=\Sigma_{j=0}^{n-1}a_jx^j A(x)=Σj=0n1ajxj在n次单位复数根处的值(共n个)
说白了就是用n个n次单位复数根代入进去A(x),求出每一个 A ( w n i ) A(w_n^i) A(wni)
y i = A ( w n i ) = Σ j = 0 n − 1 a j w n i j y_i=A(w_n^i)=\Sigma_{j=0}^{n-1}a_jw_n^{ij} yi=A(wni)=Σj=0n1ajwnij
y即为a的DFT。记为 y = D F T n ( a ) y=DFT_n(a) y=DFTn(a)
//这个a是A的系数,记得这一点。

IDFT

IDFT:即逆DFT。
进行插值操作!
现已知有n个点值对的点值表示 S = ( x 0 , y 0 ) , ( x 1 , y 1 ) , . . . , ( x n − 1 , y n − 1 ) S={(x_0,y_0),(x_1,y_1),...,(x_{n-1},y_{n-1})} S=(x0,y0),(x1,y1),...,(xn1,yn1),可以确定一个次数界为n的唯一的多项式 A ( x ) A(x) A(x)
结论: a i = 1 n Σ j = 0 n − 1 y j w n − i j a_i=\frac{1}{n}\Sigma_{j=0}^{n-1}y_jw_n^{-ij} ai=n1Σj=0n1yjwnij
证明:
这里写图片描述
实际上就是 y = V n ⋅ a ⇒ a = y ⋅ V n − 1 y=V_n·a\Rightarrow a=y·V_n^{-1} y=Vnaa=yVn1
所以将插值运算改成y与 V n − 1 V_n^{-1} Vn1的乘积。
其中, V n ∗ V n − 1 = B a s e V_n*V_n^{-1}=Base VnVn1=Base,其中 B a s e i , j = 1 , i = j , B a s e i , j = 0 , i ≠ j Base_{i,j}=1,i=j,Base_{i,j}=0,i≠j Basei,j=1,i=j,Basei,j=0,i̸=j
求证: V n − 1 的 ( i , j ) V_n^{-1}的(i,j) Vn1(i,j)处的元素为 w n − i j n \frac{w_n^{-ij}}{n} nwnij
证明?矩阵乘法学过没?
B a s e i , j = Σ k = 0 n − 1 V n [ i , k ] ∗ V n − 1 [ k , j ] = Σ k = 0 n − 1 w n i k ∗ w n − j k n = Σ k = 0 n − 1 w n k ( i − j ) n Base_{i,j}=\Sigma_{k=0}^{n-1} V_n[i,k]*V_n^{-1}[k,j]=\Sigma_{k=0}^{n-1}w_n^{ik}*\frac{w_n^{-jk}}{n}=\Sigma_{k=0}^{n-1}\frac{w_n^{k(i-j)}}{n} Basei,j=Σk=0n1Vn[i,k]Vn1[k,j]=Σk=0n1wniknwnjk=Σk=0n1nwnk(ij)
这个证明方法的主要思路:需要证明当 i = j i=j i=j时, B a s e [ i , j ] = 1 Base[i,j]=1 Base[i,j]=1,当 i ≠ j i≠j i̸=j时, B a s e [ i , j ] = 0 Base[i,j]=0 Base[i,j]=0
将式子划开,让i和j在同一个式子里扯上关系。
i = j i=j i=j时,显然, B a s e [ i , j ] = 1 Base[i,j]=1 Base[i,j]=1
否则,由求和引理, B a s e [ i , j ] = 0 Base[i,j]=0 Base[i,j]=0

应用到多项式乘法?

FFT做两个多项式A,B的乘法的过程,实际上就是将A,B分别DFT一下,即进行点值操作。
此时得到了点值对为2n的点值表示, O ( n ) O(n) O(n)扫一遍, y c [ i ] = y a [ i ] ∗ y b [ i ] y_c[i]=y_a[i]*y_b[i] yc[i]=ya[i]yb[i]
接着对C进行IDFT,完成插值操作。

FFT主要部分

1.FFT是基于分治策略的。二分。
为了方便计算,n必须满足一个大前提:n为2的正整数幂。
这种策略的优点:利用了复数单位根复数根的特殊性质。
A 0 ( x ) = a 0 + a 2 x + a 4 x 2 + . . . + a n − 2 x n / 2 − 1 A_0(x)=a_0+a_2x+a_4x^2+...+a_{n-2}x^{n/2-1} A0(x)=a0+a2x+a4x2+...+an2xn/21
A 1 ( x ) = a 1 + a 3 x + a 5 x 2 + . . . + a n − 1 x n / 2 − 1 A_1(x)=a_1+a_3x+a_5x^2+...+a_{n-1}x^{n/2-1} A1(x)=a1+a3x+a5x2+...+an1xn/21
次数界缩小了一半!
A ( x ) = A 0 ( x 2 ) + x A 1 ( x 2 ) A(x)=A_0(x^2)+xA_1(x^2) A(x)=A0(x2)+xA1(x2)

2.问题转化
原问题:求多项式 A ( x ) A(x) A(x)在n个n次单位复数根 w n 0 , w n 1 , . . . , w n n − 1 w_n^0,w_n^1,...,w_n^{n-1} wn0,wn1,...,wnn1的函数值。
现问题:求多项式 A 0 ( x ) , A 1 ( x ) A_0(x),A_1(x) A0(x),A1(x)在n个n次单位复数根的平方的函数值。
上面加粗的字是FFT的一个主要的内容。
首先出场的是折半引理。根据它,n个n次单位复数根的平方实际上是由n/2个n/2次单位复数根组成,每个根恰好出现2次。(无聊说一句,就像一个指针转了2圈)

3.得出DFT的大致步骤
(I)对于目前的 A ( x ) A(x) A(x)得出 A 0 ( x ) , A 1 ( x ) A_0(x),A_1(x) A0(x),A1(x)的系数。
(II)求出 A 0 ( x ) , A 1 ( x ) A_0(x),A_1(x) A0(x),A1(x)的点值表示。
(III) A ( x ) = A 0 ( x 2 ) + x A 1 ( x 2 ) A(x)=A_0(x^2)+xA_1(x^2) A(x)=A0(x2)+xA1(x2)
时间复杂度: T ( n ) = T ( n / 2 ) + O ( n ) T(n)=T(n/2)+O(n) T(n)=T(n/2)+O(n) O ( n   l o g   n ) O(n\ log\ n) O(n log n)

4.IDFT操作
根据 a i = 1 n Σ j = 0 n − 1 y j w n − i j a_i=\frac{1}{n}\Sigma_{j=0}^{n-1}y_jw_n^{-ij} ai=n1Σj=0n1yjwnij,直接套DFT,改某些地方即可。
(I)用 w n − 1 w_n^{-1} wn1替换 w n w_n wn,再把计算结果的每个元素除以n。
时间复杂度: T ( n ) = T ( n / 2 ) + O ( n ) T(n)=T(n/2)+O(n) T(n)=T(n/2)+O(n) O ( n   l o g   n ) O(n\ log\ n) O(n log n)

FFT的实现

上面讲了实现方法。现在侧重于实现过程。
1.点值表示的多项式乘法。
2.对乘法结果进行插值运算。

伪代码1

void FFT(a){
    n=多项式a的次数界;
    if(n=1)return a;
    w_n=(cos(2pi*i/n),sin(2pi*i/n));
    w_kn=1;
    得到a0,a1的系数。
    得到a0的DFT y0,a1的DFT y1
    for k=0 to n/2-1{
        y[k]=y0[k]+w_nk*y1[k];
        y[k+n/2]=y0[k]-w_nk*y1[k];
    }
    return y;
}

解释一下最后的循环部分。
实际上就是求出a的DFT y。
通过{}里的2行神操作,可以得出来a的DFT y。
y [ k ] = y 0 [ k ] + w n k y 1 [ k ] = A 0 ( ( w n k ) 2 ) + w n k A 1 ( ( w n k ) 2 ) = A ( w n k ) y[k]=y_0[k]+w_n^ky_1[k]=A_0((w_n^k)^2)+w_n^kA_1((w_n^k)^2)=A(w_n^k) y[k]=y0[k]+wnky1[k]=A0((wnk)2)+wnkA1((wnk)2)=A(wnk)
y [ k + n / 2 ] = y 0 [ k ] − w n k y 1 [ k ] = y k [ 0 ] + w n k + n / 2 y 1 [ k ] = A ( w n k + n / 2 ) y[k+n/2]=y_0[k]-w_n^ky_1[k]=y_k[0]+w_n^{k+n/2}y_1[k]=A(w_n^{k+n/2}) y[k+n/2]=y0[k]wnky1[k]=yk[0]+wnk+n/2y1[k]=A(wnk+n/2)
看不懂第二个式子的,尝试理解下面这一段话。
减号,代表着正好转了半圈。
还记得上文的一句话么?(无聊说一句,就像一个指针转了2圈)
想象一下这个过程,在第k时刻和第k+(n/2)时刻,指针停留的位置是一样的!!!
所以 A 1 ( ( w n k + n / 2 ) 2 ) = A 1 ( w n 2 k + n ) = A 1 ( w n 2 k ) A_1((w_n^{k+n/2})^2)=A_1(w_n^{2k+n})=A_1(w_n^{2k}) A1((wnk+n/2)2)=A1(wn2k+n)=A1(wn2k)
这招挺妙的。

程序优化

上述做法不是很好。需要优化。
PS:感谢前来观看博客的大佬们,但是到这里为止,还是不能够写出代码的。请继续读完下一部分,谢谢。
因为,根据位移和起始点的规律,可以对已有数组直接计算,不需要递归。
考虑当前问题处理的是哪些系数:
这里写图片描述

尝试发现下表的规律:
这里写图片描述
将i转化为的二进制,然后反过来设这个数为rev(i),则在开始的时候,i和rev(i)换个位置即可。
即第rev(i)个位置放 a i a_i ai
如何 O ( n ) O(n) O(n)求rev(i)?
考虑这个式子: r e v ( i ) = r e v ( i   d i v   2 ) d i v   2 + ( i   a n d   1 ) ∗ ( n   d i v   2 ) rev(i)=rev(i\ div\ 2)div\ 2+(i\ and\ 1)*(n\ div\ 2) rev(i)=rev(i div 2)div 2+(i and 1)(n div 2)
这是什么意思?
假设求出了rev(i/2),那么在i/2后面添加一个数,就相当于rev(i/2)向后挪1位。显然rev(i/2)是偶数。因为i/2的二进制第一位是0。
至于rev(i)的第一位是什么?取决于i的最后一位。
目标:不用递归,顺着做。
方法:模拟递归的回溯过程,顺序保存求出的值。注意,转移的时候,间隔是一样的。
这里写图片描述
在回溯的过程中,两个次数界为k的多项式合并为一个次数界为2k的多项式。
最后合并出来的那个那n个值就是 A ( w n 0 ) , A ( w n 1 ) , . . . , A ( w n n − 1 ) A(w_n^0),A(w_n^1),...,A(w_n^{n-1}) A(wn0),A(wn1),...,A(wnn1)
IDFT也按照上述过程就好了,不要忘记所有的元素需要除以n。

程序实现

void FFT(Cpx *y,int opz){
	int i,h,k;
	fo(i,0,m-1)if(i<Rev[i])swap(y[i],y[Rev[i]]);
	for(h=2;h<=m;h<<=1){
		Cpx wn(cos(2*Pi/h),opz*sin(2*Pi/h));
		for(i=0;i<m;i+=h){
			Cpx w(1,0);
			fo(k,i,i+(h>>1)-1){
				Cpx u=y[k],t=w*y[k+(h>>1)];
				y[k]=u+t;
				y[k+(h>>1)]=u-t;
				w=w*wn;
			}
		}
	}
	if(opz==-1){
		fo(i,0,m-1)y[i].r/=m;
	}
}
void Mul(LL *a,LL *b,LL *c){
	int i;
	fo(i,0,mo-1)f1[i]=Cpx(b[i],0);
	fo(i,mo,m-1)f1[i]=Cpx(0,0);
	fo(i,0,mo-1)f2[i]=Cpx(c[i],0);
	fo(i,mo,m-1)f2[i]=Cpx(0,0);
	FFT(f1,1);
	FFT(f2,1);
	fo(i,0,m-1)f1[i]=f1[i]*f2[i];
	FFT(f1,-1);
	fo(i,0,mo-1)a[i]=(LL)(f1[i].r+0.5)%mo;
}

参考资料

yl《快速傅里叶变换》
lyc《从多项式乘法到傅里叶变换》

附加些东西

FWT的功能:给出两个多项式 A ( x ) , B ( x ) A(x),B(x) A(x),B(x),求 C ( x ) = ∑ i ⊕ j = k A ( i ) ∗ B ( j ) C(x)=\sum_{i⊕j=k}A(i)*B(j) C(x)=ij=kA(i)B(j)
给出 ⊕ = x o r / o r / a n d ⊕=xor/or/and =xor/or/and的变换和逆变换
xor: t f ( A ) = ( t f ( A 0 ) + t f ( A 1 ) , t f ( A 0 ) − t f ( A 1 ) ) tf(A)=(tf(A_0)+tf(A_1),tf(A_0)-tf(A_1)) tf(A)=(tf(A0)+tf(A1),tf(A0)tf(A1))
u t f ( A ) = ( u t f ( A 0 + A 1 ) 2 , u t f ( A 0 − A 1 ) 2 ) utf(A)=(\frac{utf(A_0+A_1)}{2},\frac{utf(A_0-A_1)}{2}) utf(A)=(2utf(A0+A1),2utf(A0A1))
or:
t f ( A ) = ( t f ( A 0 ) , t f ( A 0 ) + t f ( A 1 ) ) tf(A)=(tf(A_0),tf(A_0)+tf(A_1)) tf(A)=(tf(A0),tf(A0)+tf(A1))
u t f ( A ) = ( u t f ( A 0 ) , u t f ( A 1 ) − u t f ( A 0 ) ) utf(A)=(utf(A_0),utf(A_1)-utf(A_0)) utf(A)=(utf(A0),utf(A1)utf(A0))
and:
t f ( A ) = ( t f ( A 0 ) + t f ( A 1 ) , t f ( A 1 ) ) tf(A)=(tf(A_0)+tf(A_1),tf(A_1)) tf(A)=(tf(A0)+tf(A1),tf(A1))
u t f ( A ) = ( u t f ( A 1 ) − u t f ( A 0 ) , u t f ( A 1 ) ) utf(A)=(utf(A_1)-utf(A_0),utf(A_1)) utf(A)=(utf(A1)utf(A0),utf(A1))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值