【总结】FFT算法在信息竞赛中的应用

本文详细介绍了FFT(快速傅里叶变换)算法,重点阐述了它如何优化卷积运算的时间复杂度,从复数和多项式的概念到离散傅里叶变换的理论推导,再到FFT算法的实现,包括递归和迭代版本,为信息竞赛中的多项式乘法提供了高效解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

FFT算法本身就是一种优化,优化(类似)卷积运算的时间复杂度
(卷积: ∑ i , j a i ∗ b j − i \sum_{i,j}a_i*b_{j-i} i,jaibji)。
FFT的本质,其实是利用复数的一些特殊性质,将一个多项式快速地在点值和系数两种表示方法间来回切换。再利用两个多项式点值表示法相乘的复杂度为O(n),来达到降时间的目的。

FFT算法的前导概念

首先介绍关于复数的一些定义及性质
复数:一种形如 a + b i a+bi a+bi的数称为复数,其中 a a a为实部, b b b为虚部, i i i为虚部单位 ( i 2 = − 1 ) (i^2=-1) i2=1

对于每个 a + b i a+bi a+bi a − b i a-bi abi为它的共轭复数

其实可以将复数看作一个二项式,其运算法则与实数概念上的二项式几乎相同,只需要注意i即可。

另外,复数也可以用向量来表示:表示为在复平面上(a,b)的向量。向量的模长为 a 2 + b 2 \sqrt{a^2+b^2} a2+b2
所以,同样地,复数还可以用三角函数来表示:即
a + b i = a 2 + b 2 ∗ ( c o s θ + s i n θ i ) a+bi=\sqrt{a^2+b^2}*(cos\theta+sin\theta i) a+bi=a2+b2 (cosθ+sinθi)
在这种表示方法下,两个复数相乘,就可以表示为其角度相加,模长相乘
p ∗ ( c o s α + s i n α i ) ∗ q ∗ ( c o s β + s i n β i ) p*(cos\alpha+sin\alpha i)*q*(cos\beta+sin\beta i) p(cosα+sinαi)q(cosβ+sinβi)
= p ∗ q ∗ ( s i n α ∗ c o s β i + c o s α ∗ s i n β i − s i n α ∗ s i n β + c o s α ∗ c o s β ) =p*q*(sin\alpha*cos\beta i+cos\alpha*sin\beta i-sin\alpha*sin\beta+cos\alpha*cos\beta) =pq(sinαcosβi+cosαsinβisinαsinβ+cosαcosβ)
= p ∗ q ∗ ( c o s ( α + β ) + s i n ( α + β ) i ) =p*q*(cos(\alpha+\beta)+sin(\alpha+\beta)i) =pq(cos(α+β)+sin(α+β)i)

然后是多项式的一些概念:
首先我们将 A ( x ) = a n x n + a n − 1 x n − 1 + a n − 2 x n − 2 . . . a 1 x + a 0 A(x)=a_nx^n+a_{n-1}x^{n-1}+a_{n-2}x^{n-2}...a_1x+a_0 A(x)=anxn+an1xn1+an2xn2...a1x+a0称为一个多项式,同时这种形式也就是系数表示法。
点值表示法:
我们可以将n-1次(包括常数一共n项)的多项式,用位于A(x)上的n个点来表示。
很容易就会发现,点值表示时,两多项式相乘复杂度为O(n)

FFT算法介绍

首先还需要引入一个单位复根的概念:
单位复根就是 ω n = 1 \omega^n=1 ωn=1的n个解
ω n k = c o s θ + s i n θ i , θ = 2 k π n ( k = 0 , 1 , 2... n − 1 ) \omega^k_n=cos\theta+sin\theta i,\theta=\frac {2kπ} n(k=0,1,2...n-1) ωnk=cosθ+sinθi,θ=n2kπk=0,1,2...n1
很容易发现: ω n k = ω n k + n \omega^k_n=\omega^{k+n}_n ωnk=ωnk+n即有周期性

离散傅里叶变换

下面开始玄学推理:
首先 1 n ∑ k = 0 n − 1 ω n v k = [ v m o d n = 0 ] \frac {1} {n}\sum_{k=0}^{n-1}\omega_n^{vk}=[v mod n =0] n1k=0n1ωnvk=[vmodn=0]
将v换为p+q
[ ( p + q ) m o d n = r ] = [ ( p + q − r ) = 0 ] = 1 n ∑ k = 0 n − 1 ω n ( p + q − r ) k = 1 n ∑ k = 0 n − 1 ω n p k ω n q k ω n − r k [(p+q) mod n =r]=[(p+q-r)=0]=\frac {1} {n}\sum_{k=0}^{n-1}\omega_n^{(p+q-r)k}=\frac {1} {n}\sum_{k=0}^{n-1}\omega_n^{pk}\omega_n^{qk}\omega_n^{-rk} [(p+q)modn=r]=[(p+qr)=0]=n1k=0n1ωn(p+qr)k=n1k=0n1ωnpkωnqkωnrk
c r c_r cr为多项式相乘后第i项的系数
有: c r = ∑ p , q [ ( p + q ) m o d   n = r ] a p b q = 1 n ∑ k = 0 n − 1 ω n p k ω n q k ω n − r k a p b q = 1 n ∑ k = 0 n − 1 ω n − r k ω n p k a p ω n q k b q c_r=\sum_{p,q}[(p+q) mod\space n =r]a_pb_q=\frac {1} {n}\sum_{k=0}^{n-1}\omega_n^{pk}\omega_n^{qk}\omega_n^{-rk}a_pb_q=\frac {1} {n}\sum_{k=0}^{n-1}\omega_n^{-rk}\omega_n^{pk}a_p\omega_n^{qk}b_q cr=p,q[(p+q)mod n=r]apbq=n1k=0n1ωnpkωnqkωnrkapbq=n1k=0n1ωnrkωnpkapωnqkbq
现在,令 d m = ∑ k = 0 n − 1 ω n m k a k d_m=\sum_{k=0}^{n-1}\omega_n^{mk}a_k dm=k=0n1ωnmkak
g m = ∑ k = 0 n − 1 ω n m k b k g_m=\sum_{k=0}^{n-1}\omega_n^{mk}b_k gm=k=0n1ωnmkbk
h m = d m × g m h_m=d_m × g_m hm=dm×gm
c m = 1 n ∑ k = 0 n − 1 ω − m k h k c_m=\frac 1 n\sum_{k=0}^{n-1}\omega^{-mk}h_k cm=n1k=0n1ωmkhk
将a,b转化为d,e以及将h转化为c,都是离散傅里叶变换,其复杂度均为 O ( n 2 ) O(n^2) O(n2)

快速傅里叶变换

现在我们要降低离散傅里叶变换的时间复杂度。
一个很简单的思路是这样的:
首先只针对于n为2的整次幂的情况
将A(x)按照奇偶次项分离,记为 A 0 ( x ) , A 1 ( x ) A_0(x),A_1(x) A0(x),A1(x)
对于任意 m < n 2 m<\frac n 2 m<2n
A ( ω n m ) = A 0 ( ω n 2 m ) + ω n m A 1 ( ω n 2 m ) = A 0 ( ω n / 2 m ) + ω n m A 1 ( ω n 2 m ) A(\omega_n^m)=A_0(\omega^{2m}_n)+\omega_n^mA_1(\omega_n^{2m})=A_0(\omega_{n/2}^m)+\omega_n^mA_1(\omega^{2m}_n) A(ωnm)=A0(ωn2m)+ωnmA1(ωn2m)=A0(ωn/2m)+ωnmA1(ωn2m)
A ( ω n m + n / 2 ) = A 0 ( ω n 2 m ) + ω n m + n / 2 A 1 ( ω n 2 m ) = A 0 ( ω n / 2 m ) − ω n m A 1 ( ω n 2 m ) A(\omega_n^{m+n/2})=A_0(\omega^{2m}_n)+\omega_n^{m+n/2}A_1(\omega_n^{2m})=A_0(\omega_{n/2}^m)-\omega_n^mA_1(\omega^{2m}_n) A(ωnm+n/2)=A0(ωn2m)+ωnm+n/2A1(ωn2m)=A0(ωn/2m)ωnmA1(ωn2m)
这样一来,我们就可以通过二分来计算离散傅里叶变换。这就是快速傅里叶变换

代码实现

首先是比较浅显易懂,与讲解结合较紧密的递归版本
这里写图片描述
缺点是常数过大。
因此,我们需要迭代版本:
观察递归时函数的参数:
( a 0 , a 1 , a 2 , a 3 , a 4 , a 5 , a 6 , a 7 ) (a_0,a_1,a_2,a_3,a_4,a_5,a_6,a_7) (a0,a1,a2,a3,a4,a5,a6,a7)
( a 0 , a 2 , a 4 , a 6 ) ( a 1 , a 3 , a 5 , a 7 ) (a_0,a_2,a_4,a_6)(a_1,a_3,a_5,a_7) (a0,a2,a4,a6)(a1,a3,a5,a7)
( a 0 , a 4 ) ( a 2 , a 6 ) ( a 1 , a 5 ) ( a 3 , a 7 ) (a_0,a_4)(a_2,a_6)(a_1,a_5)(a_3,a_7) (a0,a4)(a2,a6)(a1,a5)(a3,a7)
( a 0 ) ( a 4 ) ( a 2 ) ( a 6 ) ( a 1 ) ( a 5 ) ( a 3 ) ( a 7 ) (a_0)(a_4)(a_2)(a_6)(a_1)(a_5)(a_3)(a_7) (a0)(a4)(a2)(a6)(a1)(a5)(a3)(a7)
在二进制下,可以表示为:
000,100,010,110,001,101,011,111
如果你从前向后看,会发现其实是一个倒序进位的二进制数!
这样,就能知道每一层的参数中系数的顺序了。
迭代版的模板:

void fft(cpx *a,int f){
    int i,j,k;
    for(i=1,j=0;i<N-1;i++){//N为大于两多项式长度之和的最小的2的整次幂
        for(int d=N;j^=d>>=1,~j&d;);
        if(i<j)
            swap(a[i],a[j]);
    }
    for(i=1;i<N;i<<=1){
        cpx wn(cos(Pi/i),f*sin(Pi/i));
        for(j=0;j<N;j+=i<<1){
            cpx w(1,0);
            for(k=0;k<i;k++,w=w*wn){
            	cpx x=a[j+k],y=w*a[j+k+i];
            	a[j+k]=x+y;
            	a[j+k+i]=x-y;
			}
        }
    }
    if(f==-1)
    	for(i=0;i<N;i++)
    		a[i].r/=N;
}

看到这里,建议再往回看开头,也许能加深印象
提供一道模板题:UOJ34多项式乘法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值