学习笔记:ACM算法之FFT(个人理解向)

【音源】【PV確認用】FFT(編集有り)

引言

最近在整理自己学习的算法中发现关于多项式的算法的题已经好久没刷过了。之前也曾学习过此算法,但也仅停留在会用板子上。基于此,我花了数小时的时间重新根据其他大佬的教程自己推导了一遍,然后自己手动实现了一遍FFT的算法。在这里,我也分享一下我自己的一些笔记与推导方法。

FFT概述

FFT(Fast Fourier Transform),即快速傅里叶变换,是用来加快多项式卷积的计算过程的。想一想两个多项式,如两个3项的多项式:

A = 1 + x + 4 x 2 B = 5 + x + 4 x 2 \begin{align} A=1+x+4x^2 \tag{1}\\ B=5+x+4x^2 \tag{2} \end{align} A=1+x+4x2B=5+x+4x2(1)(2)

C = A × B C=A\times B C=A×B,如果我们朴素地计算则为:
C = 1 × ( 5 + x + 4 x 2 ) + x × ( 5 + x + 4 x 2 ) + 4 x 2 × ( 5 + x + 4 x 2 ) C=1\times(5+x+4x^2)+x\times(5+x+4x^2)+4x^2\times(5+x+4x^2) C=1×(5+x+4x2)+x×(5+x+4x2)+4x2×(5+x+4x2)

C = 5 + 6 x + 25 x 2 + 8 x 3 + 16 x 4 C=5+6x+25x^2+8x^3+16x^4 C=5+6x+25x2+8x3+16x4,需要9次计算( A A A的每一项乘 B B B都要三次)。

我们将 A , B A,B A,B展开为5项(使之与 C C C的次数相同):

A = 1 + x + 4 x 2 + 0 x 3 + 0 x 4 B = 5 + x + 4 x 2 + 0 x 3 + 0 x 4 \begin{align} A=1+x+4x^2+0x^3+0x^4 \tag{1}\\ B=5+x+4x^2+0x^3+0x^4 \tag{2} \end{align} A=1+x+4x2+0x3+0x4B=5+x+4x2+0x3+0x4(1)(2)

可推导出 C C C的每项系数 c i c_i ci A , B A,B A,B每项系数 a i , b i a_i,b_i ai,bi的关系:
c 0 = a 0 × b 0 = 5 c_0=a_0\times b_0=5 c0=a0×b0=5
c 1 = a 1 × b 0 + a 0 × b 1 = 1 + 5 = 6 c_1=a_1\times b_0 + a_0\times b_1=1+5=6 c1=a1×b0+a0×b1=1+5=6
. . . . . . ...... ......
c 4 = a 4 × b 0 + a 3 × b 1 + a 2 × b 2 + a 1 × b 3 + a 0 × b 4 = 0 + 0 + 16 + 0 + 0 = 16 c_4=a_4\times b_0 + a_3\times b_1+a_2\times b_2 + a_1\times b_3+a_0\times b_4=0+0+16+0+0=16 c4=a4×b0+a3×b1+a2×b2+a1×b3+a0×b4=0+0+16+0+0=16

即可推导出:

c i = ∑ j = 0 i a i − j b j (3) c_i=\sum_{j=0}^{i}a_{i-j}b_j \tag{3} ci=j=0iaijbj(3)

刚才我们仅仅算了三项的多项式,如果我们计算 n ( n ≥ 1 0 5 ) n(n\ge10^5) n(n105)项的多项式,如果还是按照朴素方法计算,时间复杂度为 O ( n 2 ) O(n^2) O(n2),就不能在短时间内计算出来了。这时候,FFT就能派上用场了,它能将原来的时间复杂度 O ( n 2 ) O(n^2) O(n2)优化为 O ( n l o g n ) O(nlogn) O(nlogn),也就极大的加快了计算的过程。

一些要用的前置知识

(1)点值表示法

我们考虑线代学习的 ( n − 1 ) (n-1) (n1)次多项式 f ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n − 1 x n − 1 f(x)=a_0+a_1x+a_2x^2+\cdots+a_{n-1}x^{n-1} f(x)=a0+a1x+a2x2++an1xn1
众所周知 n n n个互不相同的 x i x_i xi可以唯一确定这个多项式的系数 a i a_i ai
关于上述的证明,可以考虑将这 n n n个等式构成一个非齐次线性方程组,即:

( 1 x 0 x 0 2 ⋯ x 0 n − 2 x 0 n − 1 1 x 1 x 1 2 ⋯ x 1 n − 2 x 1 n − 1 ⋯ ⋯ ⋯ ⋯ ⋯ ⋯ 1 x n − 2 x n − 2 2 ⋯ x n − 2 n − 2 x n − 2 n − 1 1 x n − 1 x n − 1 2 ⋯ x n − 1 n − 2 x n − 1 n − 1 ) ( a 0 a 1 ⋯ a n − 2 a n − 1 ) = ( f ( x 0 ) f ( x 1 ) ⋯ f ( x n − 2 ) f ( x n − 1 ) ) \begin{pmatrix} 1 & x_0 & x_0^2 & \cdots & x_0^{n-2} & x_0^{n-1}\\ 1 & x_1 & x_1^2 & \cdots & x_1^{n-2} & x_1^{n-1}\\ \cdots & \cdots & \cdots & \cdots & \cdots & \cdots \\ 1 & x_{n-2} & x_{n-2}^2 & \cdots & x_{n-2}^{n-2} & x_{n-2}^{n-1}\\ 1 & x_{n-1} & x_{n-1}^2 & \cdots & x_{n-1}^{n-2} & x_{n-1}^{n-1}\\ \end{pmatrix} \begin{pmatrix} a_0 \\ a_1 \\ \cdots \\ a_{n-2} \\ a_{n-1} \\ \end{pmatrix}= \begin{pmatrix} f(x_0) \\ f(x_1) \\ \cdots \\ f(x_{n-2}) \\ f(x_{n-1}) \\ \end{pmatrix} 1111x0x1xn2xn1x02x12xn22xn12x0n2x1n2xn2n2xn1n2x0n1x1n1xn2n1xn1n1 a0a1an2an1 = f(x0)f(x1)f(xn2)f(xn1)

由于最左端的矩阵为 n × n n\times n n×n的矩阵,所以可以计算其行列式。又因为其为范德蒙行列式,可求其值为 ∏ 0 ≤ j < i < n ( x i − x j ) \prod_{0\le j<i<n} (x_i-x_j) 0j<i<n(xixj)。由于 x i x_i xi互不相同,则该行列式的值不为 0 0 0,即该非齐次线性方程组必有唯一解, n n n个互不相同的 x i x_i xi可以唯一确定这个多项式的系数 a i a_i ai

所以我们可以用这 n n n个互不相同的 x i x_i xi组成 n n n个点来表示这个多项式,将其写作 ( ( x 0 , f ( x 0 ) ) , ( x 1 , f ( x 1 ) ) , ⋯   , ( x n − 1 , f ( x n − 1 ) ) ) ((x_0,f(x_0)),(x_1,f(x_1)),\cdots,(x_{n-1},f(x_{n-1}))) ((x0,f(x0)),(x1,f(x1)),,(xn1,f(xn1)))

(2)复数及单位根

说起复数,相信大家应该都不陌生吧,在我们平常遇到的方程如 x 2 + 1 = 0 x^2+1=0 x2+1=0,我们容易第一反应这个方程是无解的,这是因为我们第一反应会想到这个方程在实数域上的解。不过,在复数中引入了虚数单位 i i i,且定义 i 2 = − 1 i^2=-1 i2=1,这样就可以得到上述方程其中一个解为 x = i x=i x=i

这样,我们就很容易计算两个复数的相乘:

A = a + b i B = c + d i \begin{align} A=a+bi \tag{1}\\ B=c+di \tag{2}\\ \end{align} A=a+biB=c+di(1)(2)

得到:

C = A × B = a c − b d + ( b c + a d ) i (3) C=A\times B=ac-bd+(bc+ad)i \tag{3} C=A×B=acbd+(bc+ad)i(3)

单位根指的是方程 x n = 1 x^n=1 xn=1在复数意义下的解,可以证明,这样的解有 n n n个。

我们令 ω n = e 2 π i n \omega_n=e^{\frac{2\pi i}{n}} ωn=en2πi。 这 n n n个解为 x = ω n i ( i = 0 , 1 , ⋯   , n − 1 ) x=\omega_n^i(i=0,1,\cdots,n-1) x=ωni(i=0,1,,n1) i i i 表示 ω n \omega_n ωn 的幂。从而,我们可以计算得到一下性质:

x n = ω n n i = 1 ω n k = ω 2 n 2 k ω 2 n k + n = − ω 2 n k \begin{align} &x^n=\omega_n^{ni}=1 \tag{1}\\ &\omega_n^{k}=\omega_{2n}^{2k} \tag{2}\\ &\omega_{2n}^{k+n}=-\omega_{2n}^{k} \tag{3}\\ \end{align} xn=ωnni=1ωnk=ω2n2kω2nk+n=ω2nk(1)(2)(3)

在这里简单证明一下:

( 1 ) (1) (1)式由欧拉公式 e i x = c o s x + i s i n x e^{ix}=cosx+isinx eix=cosx+isinx得:
ω n n i = e 2 π i = c o s ( 2 π ) + i s i n ( 2 π ) = 1 \omega_n^{ni}=e^{2\pi i}=cos(2\pi)+isin(2\pi)=1 ωnni=e2πi=cos(2π)+isin(2π)=1

( 2 ) (2) (2)式有:
ω 2 n 2 k = e 2 π i 2 n ⋅ 2 k = e 2 π i n ⋅ k = ω n k \omega_{2n}^{2k}=e^{\frac{2\pi i}{2n}\cdot{2k}}=e^{\frac{2\pi i}{n}\cdot{k}}=\omega_n^{k} ω2n2k=e2n2πi2k=en2πik=ωnk

( 3 ) (3) (3)式由欧拉公式有:
ω 2 n k + n = e 2 π i 2 n ⋅ ( k + n ) = e π i ⋅ e 2 π i 2 n ⋅ k = − 1 ⋅ ω 2 n k = − ω 2 n k \omega_{2n}^{k+n}=e^{\frac{2\pi i}{2n}\cdot{(k+n)}}=e^{\pi i}\cdot e^{\frac{2\pi i}{2n}\cdot k}=-1 \cdot \omega_{2n}^{k}=-\omega_{2n}^{k} ω2nk+n=e2n2πi(k+n)=eπie2n2πik=1ω2nk=ω2nk

详细步骤

多项式分组

我们令 f ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n − 1 x n − 1 f(x)=a_0+a_1x+a_2x^2+\cdots +a_{n-1}x^{n-1} f(x)=a0+a1x+a2x2++an1xn1,根据 x x x次数的奇偶将其分为:

g ( x ) = a 0 + a 2 x + a 4 x 2 + ⋯ + a n − 2 x n − 2 2 h ( x ) = a 1 + a 3 x + a 5 x 2 + ⋯ + a n − 1 x n − 2 2 \begin{align} g(x)=a_0+a_2x+a_4x^2+\cdots+a_{n-2}x^{\frac{n-2}{2}} \tag{1}\\ h(x)=a_1+a_3x+a_5x^2+\cdots+a_{n-1}x^{\frac{n-2}{2}} \tag{2}\\ \end{align} g(x)=a0+a2x+a4x2++an2x2n2h(x)=a1+a3x+a5x2++an1x2n2(1)(2)

则可以得到:

f ( x ) = g ( x 2 ) + x h ( x 2 ) \begin{align} f(x)=g(x^2)+xh(x^2) \tag{3} \end{align} f(x)=g(x2)+xh(x2)(3)

对于 ( 3 ) (3) (3)式,我们把 ω n k 和 ω n k + n 2 \omega_n^{k}和\omega_n^{k+\frac{n}{2}} ωnkωnk+2n代入,计算得:

f ( ω n k ) = g ( ( ω n k ) 2 ) + ω n k h ( ( ω n k ) 2 ) = g ( ω n 2 k ) + ω n k h ( ω n 2 k ) = g ( ω n 2 k ) + ω n k h ( ω n 2 k ) \begin{align} f(\omega_n^{k})&=g((\omega_n^{k})^2)+\omega_n^{k}h((\omega_n^{k})^2) \\ &=g(\omega_n^{2k})+\omega_n^{k}h(\omega_n^{2k}) \\ &=g(\omega_\frac{n}{2}^{k})+\omega_n^{k}h(\omega_\frac{n}{2}^{k}) \end{align} f(ωnk)=g((ωnk)2)+ωnkh((ωnk)2)=g(ωn2k)+ωnkh(ωn2k)=g(ω2nk)+ωnkh(ω2nk)

f ( ω n k + n 2 ) = f ( − ω n k ) = g ( ω n 2 k ) − ω n k h ( ω n 2 k ) \begin{align} f(\omega_n^{k+\frac{n}{2}})=f(-\omega_n^k)= g(\omega_\frac{n}{2}^{k})-\omega_n^{k}h(\omega_\frac{n}{2}^{k})\tag{1} \end{align} f(ωnk+2n)=f(ωnk)=g(ω2nk)ωnkh(ω2nk)(1)

所以在我们知道 g ( ω n 2 k ) g(\omega_\frac{n}{2}^{k}) g(ω2nk) h ( ω n 2 k ) h(\omega_\frac{n}{2}^{k}) h(ω2nk)后,就可以同时计算出 f ( ω n k ) f(\omega_n^{k}) f(ωnk) f ( ω n k + n 2 ) f(\omega_n^{k+\frac{n}{2}}) f(ωnk+2n)的值了。

FFT正变换

对于FFT正变换,得到的目标为 f ( ω n i ) ( i = 0 , 1 , ⋯   , n − 1 ) f(\omega_n^{i})(i=0,1,\cdots,n-1) f(ωni)(i=0,1,,n1) n n n个值,如果我们按照 f ( x ) f(x) f(x)多项式本身,把 ω n i \omega_n^{i} ωni 一个一个代入计算,时间复杂度还是 O ( n 2 ) O(n^2) O(n2)。这个时候,我们就要利用多项式分组中得到的性质,自底向上递推出这 n n n个值了。

在这里举个例子,令 n = 8 , f ( x ) = a 0 + a 1 x + ⋯ + a 7 x 7 n=8,f(x)=a_0+a_1x+\cdots+a_7x^7 n=8,f(x)=a0+a1x++a7x7,对于 f ( x ) f(x) f(x),我们先将其分组:

( 1 ) (1) (1)第一次分组后,
g ( x ) = a 0 + a 2 x + a 4 x 2 + a 6 x 3 h ( x ) = a 1 + a 3 x + a 5 x 2 + a 7 x 3 \begin{align} g(x)=a_0+a_2x+a_4x^2+a_6x^3 \tag{1} \\ h(x)=a_1+a_3x+a_5x^2+a_7x^3 \tag{2} \end{align} g(x)=a0+a2x+a4x2+a6x3h(x)=a1+a3x+a5x2+a7x3(1)(2)

( 2 ) (2) (2)我们将上述 g ( x ) , h ( x ) g(x),h(x) g(x),h(x),继续分组,令 g ( x ) g(x) g(x)得到的分组为 g 1 ′ ( x ) , h 1 ′ ( x ) g'_1(x),h'_1(x) g1(x),h1(x) h ( x ) h(x) h(x)得到的分组为 g 2 ′ ( x ) , h 2 ′ ( x ) g'_2(x),h'_2(x) g2(x),h2(x),即可得到:
g 1 ′ ( x ) = a 0 + a 4 x h 1 ′ ( x ) = a 1 + a 5 x g 2 ′ ( x ) = a 2 + a 6 x h 2 ′ ( x ) = a 3 + a 7 x \begin{align} g'_1(x)=a_0+a_4x \tag{1} \\ h'_1(x)=a_1+a_5x \tag{2} \\ g'_2(x)=a_2+a_6x \tag{3} \\ h'_2(x)=a_3+a_7x \tag{4} \end{align} g1(x)=a0+a4xh1(x)=a1+a5xg2(x)=a2+a6xh2(x)=a3+a7x(1)(2)(3)(4)

( 3 ) (3) (3)以此类推直到每个函数都只有一项:
g 1 ′ ′ ( x ) = a 0 h 1 ′ ′ ( x ) = a 4 g 2 ′ ′ ( x ) = a 1 h 2 ′ ′ ( x ) = a 5 g 3 ′ ′ ( x ) = a 2 h 3 ′ ′ ( x ) = a 6 g 4 ′ ′ ( x ) = a 3 h 4 ′ ′ ( x ) = a 7 \begin{align} g''_1(x)=a_0 \tag{1} \\ h''_1(x)=a_4 \tag{2} \\ g''_2(x)=a_1 \tag{3} \\ h''_2(x)=a_5 \tag{4} \\ g''_3(x)=a_2 \tag{5} \\ h''_3(x)=a_6 \tag{6} \\ g''_4(x)=a_3 \tag{7} \\ h''_4(x)=a_7 \tag{8} \end{align} g1′′(x)=a0h1′′(x)=a4g2′′(x)=a1h2′′(x)=a5g3′′(x)=a2h3′′(x)=a6g4′′(x)=a3h4′′(x)=a7(1)(2)(3)(4)(5)(6)(7)(8)

分组后就可以变换了:

这里先把前面所述的两个计算公式再重复一遍,以便阅读:
f ( ω n k ) = g ( ω n 2 k ) + ω n k h ( ω n 2 k ) f(\omega_n^k)=g(\omega_\frac{n}{2}^{k})+\omega_n^{k}h(\omega_\frac{n}{2}^{k}) f(ωnk)=g(ω2nk)+ωnkh(ω2nk)
f ( ω n k + n 2 ) = g ( ω n 2 k ) − ω n k h ( ω n 2 k ) f(\omega_n^{k+\frac{n}{2}})=g(\omega_\frac{n}{2}^{k})-\omega_n^{k}h(\omega_\frac{n}{2}^{k}) f(ωnk+2n)=g(ω2nk)ωnkh(ω2nk)

( 1 ) (1) (1)刚开始先从 ω 1 0 \omega_1^0 ω10开始计算,按照下标相同的为一组代入公式可得到:
f ′ ( ω 2 0 ) = g ′ ′ ( ω 1 0 ) + ω 2 0 h ′ ′ ( ω 1 0 ) f'(\omega_2^0)=g''(\omega_1^0)+\omega_2^0h''(\omega_1^0) f(ω20)=g′′(ω10)+ω20h′′(ω10)
f ′ ( ω 2 1 ) = g ′ ′ ( ω 1 0 ) − ω 2 0 h ′ ′ ( ω 1 0 ) f'(\omega_2^1)=g''(\omega_1^0)-\omega_2^0h''(\omega_1^0) f(ω21)=g′′(ω10)ω20h′′(ω10)

总共会计算 8 8 8次,得到的 f ′ f' f作为下一次计算所使用的 g ′ , h ′ g',h' g,h,即:
g 1 ′ ( ω 2 0 ) , h 1 ′ ( ω 2 0 ) , g 2 ′ ( ω 2 0 ) , h 2 ′ ( ω 2 0 ) , g 1 ′ ( ω 2 1 ) , h 1 ′ ( ω 2 1 ) , g 2 ′ ( ω 2 1 ) , h 2 ′ ( ω 2 1 ) g'_1(\omega_2^0),h'_1(\omega_2^0),g'_2(\omega_2^0),h'_2(\omega_2^0),g'_1(\omega_2^1),h'_1(\omega_2^1),g'_2(\omega_2^1),h'_2(\omega_2^1) g1(ω20),h1(ω20),g2(ω20),h2(ω20),g1(ω21),h1(ω21),g2(ω21),h2(ω21)

( 2 ) (2) (2)将得到的 g ′ , h ′ g',h' g,h按照 ( 1 ) (1) (1)中方法再次计算(即计算 ω 2 0 , ω 2 1 \omega_2^0,\omega_2^1 ω20,ω21),得到:
g ( ω 4 0 ) , g ( ω 4 1 ) , g ( ω 4 2 ) , g ( ω 4 3 ) , h ( ω 4 0 ) , h ( ω 4 1 ) , h ( ω 4 2 ) , h ( ω 4 3 ) g(\omega_4^0),g(\omega_4^1),g(\omega_4^2),g(\omega_4^3),h(\omega_4^0),h(\omega_4^1),h(\omega_4^2),h(\omega_4^3) g(ω40),g(ω41),g(ω42),g(ω43),h(ω40),h(ω41),h(ω42),h(ω43)
总共也是计算 8 8 8次。

( 3 ) (3) (3)最后再进行一次,即可得到 f ( ω n i ) ( i = 0 , 1 , ⋯   , n − 1 ) f(\omega_n^{i})(i=0,1,\cdots,n-1) f(ωni)(i=0,1,,n1) n n n个值。

我们可以从上述推导过程得出,对于 n n n项多项式,我们需要将其规整成 2 m 2^m 2m的形式,即 n ≤ 2 m < 2 n n\le 2^m<2n n2m<2n,对于 n ≠ 2 m n\ne2^m n=2m n n n项的多项式来说,则需在其高次项补零,即:
f ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n − 1 x n − 1 + 0 x n + 0 x n + 1 + ⋯ + 0 x 2 m − 1 f(x)=a_0+a_1x+a_2x^2+\cdots+a_{n-1}x^{n-1}+0x^n+0x^{n+1}+\cdots+0x^{2^m-1} f(x)=a0+a1x+a2x2++an1xn1+0xn+0xn+1++0x2m1

同时,对于上述自底向上的计算,可以证明每次递推的计算次数为 2 m 2^m 2m次(或 n n n次),共进行 l o g n logn logn次递推。所以对于FFT的正变换,时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

FFT逆变换

我们回顾一下点值表示法对应的非齐次线性方程组,将系数 a i a_i ai作为未知数,代入 f ( ω n i ) ( i = 0 , 1 , ⋯   , n − 1 ) f(\omega_n^{i})(i=0,1,\cdots,n-1) f(ωni)(i=0,1,,n1),可以得到:

( 1 1 1 ⋯ 1 1 1 ω n 1 ω n 2 ⋯ ω n n − 2 ω n n − 1 ⋯ ⋯ ⋯ ⋯ ⋯ ⋯ 1 ω n n − 2 ω n 2 ( n − 2 ) ⋯ ω n ( n − 2 ) 2 ω n ( n − 2 ) ( n − 1 ) 1 ω n n − 1 ω n 2 ( n − 1 ) ⋯ ω n ( n − 2 ) ( n − 1 ) ω n ( n − 1 ) 2 ) ( a 0 a 1 ⋯ a n − 2 a n − 1 ) = ( f ( 1 ) f ( ω n 1 ) ⋯ f ( ω n n − 2 ) f ( ω n n − 1 ) ) \begin{pmatrix} 1 & 1 & 1 & \cdots & 1 & 1\\ 1 & \omega_n^{1} & \omega_n^{2} & \cdots & \omega_n^{n-2} & \omega_n^{n-1}\\ \cdots & \cdots & \cdots & \cdots & \cdots & \cdots \\ 1 & \omega_n^{n-2} & \omega_n^{2(n-2)} & \cdots & \omega_n^{(n-2)^2} & \omega_n^{(n-2)(n-1)}\\ 1 & \omega_n^{n-1} & \omega_n^{2(n-1)} & \cdots & \omega_n^{(n-2)(n-1)} &\omega_n^{(n-1)^2}\\ \end{pmatrix} \begin{pmatrix} a_0 \\ a_1 \\ \cdots \\ a_{n-2} \\ a_{n-1} \\ \end{pmatrix}= \begin{pmatrix} f(1) \\ f(\omega_n^{1}) \\ \cdots \\ f(\omega_n^{n-2}) \\ f(\omega_n^{n-1}) \\ \end{pmatrix} 11111ωn1ωnn2ωnn11ωn2ωn2(n2)ωn2(n1)1ωnn2ωn(n2)2ωn(n2)(n1)1ωnn1ωn(n2)(n1)ωn(n1)2 a0a1an2an1 = f(1)f(ωn1)f(ωnn2)f(ωnn1)

由范德蒙矩阵的逆,可得到系数 a i a_i ai为:

( a 0 a 1 ⋯ a n − 2 a n − 1 ) = 1 n ( 1 1 1 ⋯ 1 1 1 ω n − 1 ω n − 2 ⋯ ω n − ( n − 2 ) ω n − ( n − 1 ) ⋯ ⋯ ⋯ ⋯ ⋯ ⋯ 1 ω n − ( n − 2 ) ω n − 2 ( n − 2 ) ⋯ ω n − ( n − 2 ) 2 ω n − ( n − 2 ) ( n − 1 ) 1 ω n − ( n − 1 ) ω n − 2 ( n − 1 ) ⋯ ω n − ( n − 2 ) ( n − 1 ) ω n − ( n − 1 ) 2 ) ( f ( 1 ) f ( ω n 1 ) ⋯ f ( ω n n − 2 ) f ( ω n n − 1 ) ) \begin{pmatrix} a_0 \\ a_1 \\ \cdots \\ a_{n-2} \\ a_{n-1} \\ \end{pmatrix}=\frac{1}{n} \begin{pmatrix} 1 & 1 & 1 & \cdots & 1 & 1\\ 1 & \omega_n^{-1} & \omega_n^{-2} & \cdots & \omega_n^{-(n-2)} & \omega_n^{-(n-1)}\\ \cdots & \cdots & \cdots & \cdots & \cdots & \cdots \\ 1 & \omega_n^{-(n-2)} & \omega_n^{-2(n-2)} & \cdots & \omega_n^{-(n-2)^2} & \omega_n^{-(n-2)(n-1)}\\ 1 & \omega_n^{-(n-1)} & \omega_n^{-2(n-1)} & \cdots & \omega_n^{-(n-2)(n-1)} &\omega_n^{-(n-1)^2}\\ \end{pmatrix} \begin{pmatrix} f(1) \\ f(\omega_n^{1}) \\ \cdots \\ f(\omega_n^{n-2}) \\ f(\omega_n^{n-1}) \\ \end{pmatrix} a0a1an2an1 =n1 11111ωn1ωn(n2)ωn(n1)1ωn2ωn2(n2)ωn2(n1)1ωn(n2)ωn(n2)2ωn(n2)(n1)1ωn(n1)ωn(n2)(n1)ωn(n1)2 f(1)f(ωn1)f(ωnn2)f(ωnn1)

即可得到 a i = 1 n ( f ( 1 ) + f ( ω n 1 ) ω n − i + f ( ω n 2 ) ( ω n − i ) 2 + ⋯ + f ( ω n n − 1 ) ( ω n − i ) n − 1 ) a_i=\frac{1}{n}(f(1)+f( \omega_n^1)\omega_n^{-i}+f( \omega_n^2)(\omega_n^{-i})^2+\cdots+f( \omega_n^{n-1})(\omega_n^{-i})^{n-1}) ai=n1(f(1)+f(ωn1)ωni+f(ωn2)(ωni)2++f(ωnn1)(ωni)n1),可以看成 a i = 1 n f ( ω n − i ) a_i=\frac{1}{n}f(\omega_n^{-i}) ai=n1f(ωni)的形式,同正变换一样,再做一次FFT即可。

个人用板子

本人使用的是递归版本的FFT,来源:快速傅里叶变换

#include <math.h>
#include <iostream>
#include <complex>

using namespace std;

typedef complex<double> Comp;

const Comp I(0, 1); //Comp I(a, b) = a + b * i
const int maxn = 1 << 20; //根据实际题目大小自行调整
const double M_PI_ = acos(-1.0);

Comp temp[maxn], f[maxn];

void DFT(Comp* f, int n, int rev) { //0 ~ n - 1 共n项 , rev = 1, DFT; rev = -1, IDFT, 计算f(wnk)
	if (n == 1) { //常数项
		return;
	}
	for (int i = 0; i < n; i++) {
		temp[i] = f[i];
	}
	for (int i = 0; i < n; i++) {
		if (i & 1) {
			f[i / 2 + n / 2] = temp[i]; // 奇数位置范围:n / 2 ~ n - 1;
		}
		else {
			f[i / 2] = temp[i]; // 偶数位置范围:0 ~ n / 2 - 1;
		}
	}
	Comp* g = f, * h = f + n / 2;
	Comp cur(1, 0);
	Comp step = exp(I * (2 * M_PI_ / n * rev));
	DFT(g, n / 2, rev);
	DFT(h, n / 2, rev);
	for (int k = 0; k < n / 2; ++k) {
		temp[k] = g[k] + cur * h[k];
		temp[k + n / 2] = g[k] - cur * h[k];
		cur *= step;
	}
	for (int i = 0; i < n; ++i) {
		f[i] = temp[i];
	}
}

两道例题

P3803 【模板】多项式乘法(FFT)

思路:分别对多项式 F ( x ) F(x) F(x) G ( x ) G(x) G(x)进行FFT正变换,得到的值分别相乘,作为逆变换的 f f f,最后逆变换即可。

#include <math.h>
#include <iostream>
#include <complex>

using namespace std;

typedef complex<double> Comp;

const Comp I(0, 1); //Comp I(a, b) = a + b * i
const int maxn = 1 << 24;
const double M_PI_ = 3.1415926535;

Comp temp[maxn], f[maxn], g[maxn];

void DFT(Comp* f, int n, int rev) { //0 ~ n - 1 共n项 , rev = 1, DFT; rev = -1, IDFT, 计算f(wnk)
	if (n == 1) { //常数项
		return;
	}
	for (int i = 0; i < n; i++) {
		temp[i] = f[i];
	}
	for (int i = 0; i < n; i++) {
		if (i & 1) {
			f[i / 2 + n / 2] = temp[i]; // 奇数位置范围:n / 2 ~ n - 1;
		}
		else {
			f[i / 2] = temp[i]; // 偶数位置范围:0 ~ n / 2 - 1;
		}
	}
	Comp* g = f, * h = f + n / 2;
	Comp cur(1, 0);
	Comp step = exp(I * (2 * M_PI_ / n * rev));
	DFT(g, n / 2, rev);
	DFT(h, n / 2, rev);
	for (int k = 0; k < n / 2; ++k) {
		temp[k] = g[k] + cur * h[k];
		temp[k + n / 2] = g[k] - cur * h[k];
		cur *= step;
	}
	for (int i = 0; i < n; ++i) {
		f[i] = temp[i];
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n, m;
	cin >> n >> m;
	int size = 1;
	int max_ = n + m;
	while (size <= max_) {
		size <<= 1;
	}
	for (int i = 0; i <= n; i++) {
		int a;
		cin >> a;
		f[i].real(a);
	}
	for (int i = 0; i <= m; i++) {
		int a;
		cin >> a;
		g[i].real(a);
	}
	DFT(f, size, 1);
	DFT(g, size, 1);
	for (int i = 0; i < size; i++) {
		double freal = f[i].real(), greal = g[i].real(), fimag = f[i].imag(), gimag = g[i].imag();
		f[i].real(freal * greal - fimag * gimag);
		f[i].imag(freal * gimag + fimag * greal);
	}
	DFT(f, size, -1);
	for (int i = 0; i <= n + m; i++) {
		cout << (int)(f[i].real() / size + 0.5) << " ";
	}
	return 0;
}

P3338 [ZJOI2014]力

思路:把公式转换为卷积的形式,最终可得到 E j = ∑ i = 0 j f ( i ) g ( j − i ) − ∑ i = 0 n − j + 1 g ( i ) h ( n − j + 1 − i ) E_j=\sum_{i=0}^{j}f(i)g(j-i)-\sum_{i=0}^{n-j+1}g(i)h(n-j+1-i) Ej=i=0jf(i)g(ji)i=0nj+1g(i)h(nj+1i),其中 h h h f f f n n n 0 0 0的序列(即反向序列)。

#include <math.h>
#include <iostream>
#include <complex>

using namespace std;

typedef complex<double> Comp;

const Comp I(0, 1); //Comp I(a, b) = a + b * i
const int maxn = 1 << 20; //根据实际题目大小自行调整
const double M_PI_ = acos(-1.0);

Comp temp[maxn], f[maxn], g[maxn], h[maxn];

void DFT(Comp* f, int n, int rev) { //0 ~ n - 1 共n项 , rev = 1, DFT; rev = -1, IDFT, 计算f(wnk)
	if (n == 1) { //常数项
		return;
	}
	for (int i = 0; i < n; i++) {
		temp[i] = f[i];
	}
	for (int i = 0; i < n; i++) {
		if (i & 1) {
			f[i / 2 + n / 2] = temp[i]; // 奇数位置范围:n / 2 ~ n - 1;
		}
		else {
			f[i / 2] = temp[i]; // 偶数位置范围:0 ~ n / 2 - 1;
		}
	}
	Comp* g = f, * h = f + n / 2;
	Comp cur(1, 0);
	Comp step = exp(I * (2 * M_PI_ / n * rev));
	DFT(g, n / 2, rev);
	DFT(h, n / 2, rev);
	for (int k = 0; k < n / 2; ++k) {
		temp[k] = g[k] + cur * h[k];
		temp[k + n / 2] = g[k] - cur * h[k];
		cur *= step;
	}
	for (int i = 0; i < n; ++i) {
		f[i] = temp[i];
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n;
	cin >> n;
	int size = 1;
	int max_ = 2 * n;
	while (size <= max_) {
		size <<= 1;
	}
	for (int i = 1; i <= n; i++) {
		double a;
		cin >> a;
		f[i].real(a);
		h[n - i + 1].real(a);
		g[i].real(1.0 / (double)i / (double)i);
	}
	DFT(f, size, 1);
	DFT(g, size, 1);
	DFT(h, size, 1);
	for (int i = 0; i <= size; i++) {
		double freal = f[i].real(), greal = g[i].real(), hreal = h[i].real();
		double fimag = f[i].imag(), gimag = g[i].imag(), himag = h[i].imag();
		f[i].real(freal * greal - fimag * gimag);
		f[i].imag(freal * gimag + fimag * greal);
		g[i].real(hreal * greal - himag * gimag);
		g[i].imag(hreal * gimag + himag * greal);
	}
	DFT(f, size, -1);
	DFT(g, size, -1);
	for (int i = 1; i <= n; i++) {
		printf("%.6lf\n", (f[i].real() - g[n - i + 1].real()) / size);
	}
	return 0;
}

总结

这应该是我第一次正式记录自己学习的算法,目的还是为了加深对这个变换思想的理解。此博客仅为自己的学习记录,可能有一些理解不同或者错误的地方,也请各位大佬多多指教啦~
顺便浇浇FFT的紫谱和白谱喵

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值