[Algo] 고속 푸리에 변환 (Fast Fourier Transform)

Park Yeongseo·2024년 8월 12일

Algorithm

목록 보기
11/20
post-thumbnail

Introduction to Algorithm 4th edition을 번역 및 정리한 글입니다.

1. 다항식과 FFT

직관적으로 생각할 때, 차수가 nn인 두 다항식을 더하는 데에는 O(n)O(n), 곱하는 데에는 O(n2)O(n^2)의 시간이 걸린다. 그런데 두 다항식을 곱하는 데 걸리는 시간을 O(nlog⁡n)O(n\log n)으로 줄이는 방법이 있으니, 바로 고속 푸리에 변환(fast Fourier transform, FFT) 이다.

(1) 다항식

대수적 수체 FF의 변수 xx에 대한 다항식은 함수 A(x)A(x)를 다음과 같이 합의 형식으로 표현한다.

A(x)=∑j=0n=1ajxjA(x) = \sum_{j=0}^{n=1}a_jx^j

여기서 값 a0, a1, ..., an−1a_0,\ a_1,\ ...,\ a_{n-1}은 다항식의 계수들이다. 계수와 xx는 체 FF에서 가져온다. 다항식 A(x)A(x) 는 차수 kk를 가지고 있다. 다항식은 가장 높은, 0이 아닌 계수가 aka_k일 때 kk 차수라고 말하며, 다음과 같이 나타낸다.
degree(A)=kdegree(A) = k
어떤 다항식의 degree-bound가 nn이라는 말은, 이 다항식의 차수가 nn 미만이라는 것이다. 즉 00 ~ n−1n-1의 차수를 가지는 모든 다항식은 degree-bound가 nn이라고 할 수 있다..

degree-bound가 nn인 두 다항식 A(x),B(x)A(x), B(x)를 생각해보자. 두 다항식의 합 C(x)C(x) 또한 degree-bound nn이다.

한편, A(x),B(x)A(x), B(x)이 degree-bound nn이라 할 때, 두 다항식의 곱 C′(x)C'(x)는 degree-bound 2n−12n-1의 다항식이 된다.

C(x)=∑j=02n−2cjxj,wherecj=∑k=0jakbj−k\begin{aligned} &C(x) = \sum_{j=0}^{2n-2}c_jx^j, \\&where \\&c_j = \sum_{k=0}^ja_kb_{j-k} \end{aligned}

만약 A의 차수가 nan_a이고 B의 차수가 nbn_b이면 C′C'의 차수는 na+nbn_a + n_b이다. 다만 degree-bound가 2n−12n-1인 다항식은 degree-bound 2n2n이기도 하기에, 간단하게 다항식 곱의 degree-bound는 2n2n이라고 한다.

(2) 다항식의 표현

다항식은 여러 방식으로 표현할 수 있다.

(2-1) 계수 표현 (Coefficient representation)

다항식 A(x)=∑i=0n−1aixiA(x) = \sum_{i=0}^{n-1}a_ix^i 는 다음과 같이 그 계수들을 모은 벡터로 표현할 수 있다.

a=(a0,a1,...,an−1)a = (a_0, a_1, ..., a_{n-1})

점 x0x_0에서의 A(x0)A(x_0) 값을 계산하는 등의 연산은 계수 표현을 이용하면 간편하다. A(x0)A(x_0)은 다음과 같이 O(n)O(n)의 시간에 계산할 수 있다(Horner's rule) .

A(x0)=a0+x0(a1+x0(a2+...+(an−2+x0(an−1))))A(x0) = a0 + x0(a1 + x0 (a2 + ... + (a_{n-2} + x0(a_{n-1}))))

두 다항식을 더하는 것도 O(n)O(n) 시간에 가능하다.

하지만 단순히 두 다항식을 곱하는 건 O(n2)O(n^2) 시간이 걸린다. 벡터 aa의 각 계수들(즉 다항식 A(x)A(x)의 계수들)을 벡터 bb(다항식 B(x)B(x)의 계수들)에 있는 계수들에 각각 곱해줘야하기 때문이다.

(2-2) 점-값 표현 (Point-value representation)

degree-bound nn인 다항식 A(x)A(x)은 nn개의 point-value 쌍의 집합으로도 표현할 수 있다.

{(x0,y0),(x1,y1),...,(xn−1,yn−1)}\{(x0, y0), (x1, y1), ..., (x_{n-1}, y_{n-1})\}

여기서 각 xkx_k들은 서로 다르며, yk=A(xk)y_k = A(x_k)를 만족한다.

하나의 다항식은 여러 p-v 표현을 가질 수 있는데, nn개의 서로 다른 점 x0,...,xn−1x_0, ..., x_{n-1}을 뽑아 A(x0),...,A(xn−1)A(x_0), ..., A(x_{n-1})을 계산하면 되기 때문이다..

앞서의 Horner's rule을 사용하는 경우 점 xkx_k에서의 값 A(xk)A(x_k)는 O(n)O(n)에 계산할 수 있기에, nn개의 점에 대한 값을 구해 p-v 표현을 만드는 경우의 시간복잡도는 O(n2)O(n^2)이 될 것이다. 하지만 이 xkx_k들을 어떻게 잘 정하면 O(nlog⁡n)O(n \log{n})의 시간에 이를 계산할 수 있게 된다.

p-v 표현으로부터 역으로 다항식을 결정하는 것을 보간법(interpolation)이라 한다. 앞서 말했듯 한 다항식은 여러 p-v표현을 가질 수 있다. 하지만 nn개의 점-값 쌍으로 이루어진 집합이 주어진 경우, degree-bound nn인 다항식은 유일하게 결정될 수 있다.

(2-2.1)Th. Interpolating polynomial의 유일성

서로 다른 xkx_k에 대해, 집합 {(x0,y0),...,(xn−1,yn−1)}\{(x_0, y_0), ..., (x_{n-1}, y_{n-1})\}이 주어졌을 때, 각 k=0,1,...,n−1k = 0, 1, ..., n-1에 대해 yk=A(xk)y_k = A(x_k)를 만족하는 degree-bound nn의 다항식 A(x)A(x)는 유일하다.

(1x0x02...x0n−11x1x12...x1n−1...1xn−1xn−12...xn−1n−1)(a0a1...an−1)=(y0y1...yn−1)\begin{pmatrix} 1 & x_0 & x_0^2 &... &x_0^{n-1}\\ 1 & x_1 & x_1^2 &... &x_1^{n-1}\\ ...\\ 1 & x_{n-1} & x_{n-1}^2 &... &x_{n-1}^{n-1}\\ \end{pmatrix} \begin{pmatrix} a_0 \\ a_1 \\ ...\\ a_{n-1} \end{pmatrix} = \begin{pmatrix} y_0 \\ y_1 \\ ...\\ y_{n-1} \end{pmatrix}

yk=A(xk)y_k = A(x_k)는 위의 항등식과 동치다. 여기서 가장 왼쪽의 행렬을 방데르몽드 행렬(Vander-monde Matrix)이라 하며 V(x0,x1,...,xn−1)V(x_0, x_1, ..., x_{n-1})로 표기한다.

방데르몽드 행렬의 행렬식은 ∏j<k(xk−xj)\prod_{j <k} (x_k - x_j)이며, 따라서 각 xkx_k가 서로 다른 경우 00이 아닌 값이 된다. 즉 가역(invertible)이다.

이를 통해 우리는 다음과 같이 계수 행렬 aa를 구할 수 있다.

a=V(x0,x1,...,xn−1)−1ya = V(x_0, x_1, ..., x_{n-1})^{-1}y

p-v 표현을 이용하면 간편한 다항식 연산들이 있다.

degree-bound nn의 다항식 A(x),B(x)A(x), B(x)가 있다고 하자.점 x0,...,xn−1x_0, ..., x_{n-1}이 주어지고, 각 점에 대해 A(xk),B(xk)A(x_k), B(x_k)가 계산되어 있을 때, 그 nn개의 같은 점에서의 다항식 합C(x)=A(x)+B(x)C(x) = A(x) + B(x)을 알고 싶다고 하자. C(xk)=A(xk)+B(xk)C(x_k) = A(x_k) + B(x_k)이므로 nn개 점의 값을 O(n)O(n)의 시간에 구할 수 있다.

p-v 표현을 이용하는 경우, 계수 표현과 달리, 다항식 곱의 경우도 마찬가지로 C′(x)=A(x)B(x)C'(x) = A(x) B(x)일 때, C′(xk)=A(xk)B(xk)C'(x_k) = A(x_k) B(x_k)이므로 nn개의 점에서의 다항식 곱의 값도 O(n)O(n)의 시간에 구할 수 있다.

우리의 목표는 두 다항식의 곱을 빠르게 구하는 것이었기에 p-v 표현을 쓰는 게 더 적절해보인다. 다만 degree(C′)=degree(A)+degree(B)degree(C') = degree(A) + degree(B)이므로 A(x),B(x)A(x), B(x)가 degree-bound nn일 때, C(x)C(x)는 degree-bound 2n2n이다. 우리가 계산을 통해 얻은 C(xk)C(x_k)의 값들은 nn개인데, 보간법을 이용해 C(x)C(x)를 알아내기 위해서는 적어도 2n2n개의 점이 필요하다. 어떻게 해야할까?

그냥 2n2n개의 점들로 시작하면 된다. 2n2n개의 점 x0,...,x2n−1x_0, ..., x_{2n-1}에 대해 A(xk),B(xk)A(x_k), B(x_k)의 값을 찾아놓으면 된다.

{(x0,A(x0)),...,(x2n−1,A(x2n−1))}{(x0,B(x0)),...,(x2n−1,B(x2n−1))}\begin{aligned} \{(x_0, A(x_0)), ..., (x_{2n-1}, A(x_{2n-1}))\}\\ \{(x_0, B(x_0)), ..., (x_{2n-1}, B(x_{2n-1}))\}\\ \end{aligned}

nn개의 각 xkx_k에 대해 C(xk)=A(xk)B(xk)C(x_k) = A(x_k)B(x_k)를 구하는 데에는 O(2n)=O(n)O(2n) = O(n)의 시간이 걸린다. O(n2)O(n^2)보다 훨씬 빠르다.

(2-3) Fast multiplication of polynomials in coefficient form

그렇다면 두 다항식의 계수들이 주어졌을 때, 어떻게 해야 둘의 곱을 빠르게 구할 수 있을까? 이는 계수 표현과 p-v 표현을 서로 얼마나 빠르게 변환할 수 있는지에 달려있다. 위 그림에서 볼 수 있듯, 계수 표현과 p-v 표현을 빠르게 변환할 수 있다면, 전체 시간복잡도는 O(nlog⁡n)O(n\log{n})이 될 수 있다.

앞서 말했듯, 보통 점 xkx_k에서의 값 A(xk)A(x_k)는 O(n)O(n)에 계산될 수 있으므로, nn개의 점에 대한 p-v 표현을 만드는 데에는 O(n2)O(n^2)이 걸린다. 하지만 이 xkx_k들을 어떻게 잘 정하면 O(nlog⁡n)O(n\log{n})의 시간에 이를 계산할 수 있다.

전체 플로우는 정리하면 다음과 같다.
1. A(x),B(x)A(x), B(x)에 대한 degree-bound 2n2n의 계수 표현을 만든다. 각 다항식의 차수보다 높은 차수에 계수가 00인 항들을 추가하면 된다.
2. 2n2n개의 점에서의 A(x),B(x)A(x), B(x) 값들을 계산한다.
3. 2n2n개의 각 점에서 A(xk)A(x_k)와 B(xk)B(x_k)를 곱해 C(xk)C(x_k)를 찾는다.
4. 보간법을 이용해 C(x)C(x)에 대한 계수 표현을 찾아낸다.

1번, 3번 단계는 O(n)O(n), 2, 4번은 O(nlogn)O(n log{n})의 시간이 걸려, 총 시간복잡도는 O(nlogn)O(nlog{n})이 된다.

2. DFT와 FFT

(1) Complex roots of unity

1의 nn제곱근(complex nnth root of unity) ω\omega는 ωn=1\omega^n = 1이 되는 복소수로, 정확히 nn개가 있다(e2πik/n.k=0,1,...,n−1)e^{2\pi i k/n}. k = 0, 1, ..., n-1).

eiu=cos⁡(u)+isin⁡(u)e^{iu} = \cos(u) + i \sin(u)임을 생각해보자. nn개의 1의 nn제곱근들은 복소평면의 원점을 중심으로 하는 단위 원을 정확히 nn등분하고 있는 것으로 생각할 수 있다.

여기서 ωn=e2πi/n\omega_n = e^{2 \pi i/n} 을 principal nnth root of unity라 하며, 나머지는 ωn\omega_n의 제곱꼴로 나타낼 수 있다. 즉 nn개의 1의 nn제곱근은 다음과 같다.

ωn0,ωn1,...,ωnn−1\omega_n^0, \omega_n^1, ..., \omega_n^{n-1}

여기서 ωnn=ωn0=1\omega_n^n = \omega_n^0 = 1이므로, ωnjωnk=ωnj+k=ωn(j+k)mod  n\omega_n^j \omega_n^k = \omega_n^{j+k} = \omega_n^{(j+k) \mod{n}}임을 알 수 있다. 마찬가지로 ωn−1=ωnn−1\omega_n^{-1} = \omega_n^{n-1}이 된다. 아래의 보조 정리들은 1의 nn제곱근이 갖는 몇 가지 기본적인 성질들이다.

(1-2) Cancellation Lemma

정수 n>0,k≥0,d>0n >0, k \geq 0, d >0에 대해, ωdndk=ωnk\omega_{dn}^{dk} = \omega_n^k다.

ωdndk=(e2πi/dn)dk=(e2πi/n)k=ωnk\begin{aligned} \omega_{dn}^{dk} &= (e^{2 \pi i / dn})^{dk}\\ &= (e^{2\pi i /n})^ k \\ &= \omega_n^k \end{aligned}

(1-2.1) 따름정리

짝수 n>0n > 0에 대해, ωnn/2=ω2=−1\omega_n^{n/2} = \omega_2 = -1이다.

−1=ω2=ω21=ω2(n/2)1(n/2)=ωnn/2-1 = \omega_2 = \omega_2^1 = \omega_{2 (n / 2)}^{1(n / 2)} = \omega_n^{n/2}

(1-3) Halving Lemma

n>0n > 0이 짝수일 때, nn개의 1의 nn 거듭제곱근들을 제곱하면 n/2n/2개의 1의 n/2n/2 거듭제곱근들을 얻을 수 있다.

Cancellation lemma에 의해 00이 아닌 정수 kk에 대해 (ωnk)2=ωn/2k(\omega_n^k)^2 = \omega_{n/2}^k이다.

(ωnk+n/2)2=ωn2k+n=ωn2kωnn=ωn2k=(ωnk)2\begin{aligned} (\omega_n^{k+n/2})^2 &= \omega_n^{2k +n}\\ &= \omega_n^{2k} \omega_n^n \\ &= \omega_n^{2k} \\ &= (\omega_n^k)^2 \end{aligned}

이므로 (ωnk)2=(ωnk+n/2)2(\omega_n^k)^2 = (\omega_n^{k + n/2})^2이다.

이 렘마는 다항식의 계수 표현과 점-값 표현 변환을 O(nlog⁡n)O(n \log{n})에 할 수 있도록 하는 분할 정복을 위해 쓰인다.

(1-4) Summation Lemma

모든 정수 n≥1n \geq 1과 nn으로 나누어지지 않는 00이 아닌 모든 정수 kk에 대해,
$$ \sum_{j=0}^{n-1}(\omega _n^k)^j = 0$$> 이다.

등비수열의 합공식을 사용할 수 있다.

∑j=0n−1(ωnk)j=(ωnk)n−1ωnk−1=(ωnn)k−1ωnk−1=(1)k−1ωnk−1=0\begin{aligned} \sum_{j=0}^{n-1}(\omega_n^k)^j &= \frac{(\omega_n^k)^n - 1}{\omega_n^k-1}\\ &= \frac{(\omega_n^n)^k - 1}{\omega_n^k-1}\\ &= \frac{(1)^k - 1}{\omega_n^k - 1} \\ &= 0 \end{aligned}

n∤kn \not{|} k이므로 ωnk≠1\omega_n^k \neq 1이기에 가능하다.

(2) The DFT

degree-bound nn인 다항식 A(x)=∑j=0n−1ajxjA(x) = \sum_{j = 0}^{n-1} a_j x_j의 ωn0,...,ωnn−1\omega_n^0, ... , \omega_n^{n-1}에서의 값을 계산한다.
AA의 계수 표현은 a=(a0,a1,...,an−1)a = (a_0, a_1, ..., a_{n-1})이다. k=0,1,...,n−1k = 0, 1, ..., n - 1일 때, yk=A(ωnk)y_k = A(\omega_n^k)라 하자.

yk=A(ωnk)=∑j=0n−1ajωnkj\begin{aligned} y_k &= A(\omega_n^k)\\ &= \sum_{j=0}^{n-1}a_j\omega_n^{kj} \end{aligned}

벡터 y=(y0,y1,...,yn−1)y = (y_0, y_1, ..., y_{n-1})을 계수 벡터 aa의 DFT라고 하며, y=DFTn(a)y = DFT_n(a)로 표기한다.

(3) The FFT

FFT는 직관적인 방법과 달리, 1의 거듭제곱근의 특별한 성질을 이용해 O(nlog⁡n)O(n \log n)의 시간에 DFTn(a)DFT_n(a)를 계산한다. 다만 여기서는 nn이 22의 제곱수라고 가정한다.

FFT는 분할 정복을 사용한다. 주어진 degree-bound nn의 다항식 A(x)A(x)을 다음과 같이 두 개의 degree-bound n/2n/2 다항식으로 분할해보자.

Aeven(x)=a0+a2x+a4x2+...+an−2xn/2−1Aodd(x)=a1+a3x+a5x2+...+an−1xn/2−1\begin{aligned} A^{even}(x) = a_0 + a_2x + a_4x^2 + ... + a_{n-2}x^{n/2-1}\\ A^{odd}(x) = a_1 + a_3x + a_5x^2 + ... + a_{n-1}x^{n/2-1}\\ \end{aligned}

즉 A(x)=Aeven(x2)+xAodd(x2)A(x) = A^{even}(x^2) + xA^{odd}(x^2)가 된다.

이제 ωn0,ωn1,...,ωnn−1\omega_n^0, \omega_n^1, ..., \omega_n^{n-1}에서의 A(x)A(x) 값을 구하는 문제는 다음과 같은 문제로 줄어든다.

  1. nn개 점 (ωn0)2,(ωn1)2,...,(ωnn−1)2(\omega_n^0)^2, (\omega_n^1)^2, ..., (\omega_n^{n-1})^2에서의 Aeven(x),Aodd(x)A^{even}(x), A^{odd}(x) 값을 계산하고
    • Halving Lemma에서 봤듯 n>0n > 0이 짝수일 때, nn개의 1의 nn 거듭제곱근들을 제곱하면 n/2n/2개의 1의 n/2n/2 거듭제곱근들을 얻을 수 있고, 각 n/2n/2 거듭제곱근들은 (부호만 바꿔서) 정확히 두 번 나타나게 되므로 nn개의 점이 아니라 n/2n/2개의 점에 대한 계산을 하게 된다.
  2. 그 결과를 가지고 A(x)=Aeven(x2)+xAodd(x2)A(x) = A^{even}(x^2) + xA^{odd}(x^2)로 합치기

Aeven(x)A^{even}(x)와 Aodd(x)A^{odd}(x)에 대해서도 마찬가지의 방식으로 분할정복을 해나가면 된다. 재귀의 base case는 DFT를 실행할 원소가 하나 밖에 없을 때다. y0=a0ω10=a0y_0 = a_0 \omega_1^0 = a_0이 되기 때문이다

FFT의 pseudo-code는 위와 같다. 길이를 계속해서 반으로 줄여가면서 가고 있기에 O(nlog⁡n)O(n \log{n})의 시간복잡도를 가짐을 바로 볼 수있다.

(3-1) Interpolation at the complex roots of unity

위와 같이 O(nlog⁡n)O(n\log{n})의 시간에 계수 표현에서 점-값 표현으로 변환했으니, 이번에는 보간법을 통해 점-값 표현에서 계수 표현으로 변환할 수 있기만 하면 된다.

(111...11ωnωn2...ωnn−1...1ωnn−1ωn2(n−1)...ωn(n−1)(n−1))(a0a1...an−1)=(y0y1...yn−1)\begin{pmatrix} 1 & 1 & 1 &... &1\\ 1 & \omega_n & \omega_n^2 &... &\omega_n^{n-1}\\ ...\\ 1 & \omega_n^{n-1} & \omega_n^{2(n-1)} &... &\omega_n^{(n-1)(n-1)}\\ \end{pmatrix} \begin{pmatrix} a_0 \\ a_1 \\ ...\\ a_{n-1} \end{pmatrix} = \begin{pmatrix} y_0 \\ y_1 \\ ...\\ y_{n-1} \end{pmatrix}

DFT는 위와 같은 행렬 항등식으로 나타낼 수 있다. 여기의 방데르몽드 행렬 VnV_n의 역함수를 yy에 곱해주면 계수 행렬 aa를 얻을 수 있게 된다.

(3-2) Theorem

j,k=0,1,...,n−1j, k = 0, 1, ..., n-1에 대해 , Vn−1V_n^{-1}의 (j,k)(j,k) 성분은 ωn−jk/n\omega_n^{-jk}/n이다.

Vn−1Vn=InV_n^{-1}V_n = I_n임을 이용한다. Vn−1VnV_n^{-1}V_n의 (k,k′)(k, k') 성분은

[Vn−1Vn]kk′=∑j=0n−1(ωn−jk/n)(ωnjk′)=∑j=0n−1ωnj(k′−k)/n\begin{aligned} {[}V_n^{-1} V_n{]}_{kk'} &= \sum_{j=0}^{n-1}(\omega_n^{-jk}/n)(\omega_n^{jk'})\\ &= \sum_{j=0}^{n-1}\omega_n^{j(k'-k)}/n \end{aligned}

$ k′≠kk'\neq k이면 위 값은 0이고, k=k′k = k'이면 1이다. 사실 Summation Lemma에 따르면 n∤k′−kn \not{|} k' -k
여야 하지만 −n+1≤k′−k≤n−1-n + 1 \leq k' -k \leq n - 1이므로 k≠k′k \neq k'만을 생각해도 좋다.

Vn−1V_n^{-1}을 정의했으니 DFTn−1(y)DFT_n^{-1}(y)는 다음과 같이 얻을 수 있다.

aj=∑k=0n−1ykω−jkn=1n∑k=0n−1ykωn−kj\begin {aligned} a_j &= \sum_{k=0}^{n-1}y_k\frac{\omega^{-jk}}{n}\\ &= \frac{1}{n}\sum_{k = 0}^{n - 1}y_k\omega_n^{-kj} \end {aligned}

앞서 DFT에서 보았던 식과 아주 비슷하다. 때문에 비슷하게 FFT를 적절하게 변형하면 보간도 O(nlog⁡n)O(n \log{n})의 시간에 할 수 있게 된다.

Convolution Theorem

길이 nn의 두 벡터 a,ba, b에 대해, nn이 22의 제곱수일 때,
a⊗b=DFT2n−1(DFT2n(a)⋅DFT2n(b)a \otimes b = DFT_{2n}^{-1}(DFT_{2n}(a) \cdot DFT_{2n}(b)
다. 단 a,ba, b는 길이가 2n2n이 되도록 0으로 패딩하고, ⋅\cdot은 벡터의 내적(dot product)이다.

3. Code

typedef complex<double> cpx;
const double PI = acos(-1);

vector<cpx> a, b;

/*
	계수 표현을 p-v 표현으로 변경하는 FFT
*/
void FFT(vector<cpx> &a, cpx omega_n) {
	int N = a.size();
	// base case. 아무 것도 하지 않는다.
	if (a.size() == 1) return; 

	// 홀수 차항, 짝수 차항을 나눈다
	vector<cpx> even(N / 2), odd(N / 2);

	for (int i = 0; i < N; i++) {
		if (i & 1) odd[i / 2] = a[i];
		else even[i / 2] = a[i];
	}

	// 각각에 대해 FFT. 분할 정복.
	// A(x) = A^{even}(x^2) + x * A^{odd}(x^2)
	FFT(even, omega_n * omega_n);
	FFT(odd, omega_n * omega_n);

	cpx omega(1, 0);

	// Halving Lemma.
	// 따름정리에 따라 w_n^{n/2 + k} = -w_n^k이므로
	for (int i = 0; i < N / 2; i++) {
		a[i] = even[i] + omega * odd[i];
		a[i + N / 2] = even[i] - omega * odd[i];
		omega *= omega_n;
	}
}

/*
	a와 b는 계수 표현이다.
	a[i] = A(x)의 i차 항의 계수, b[i] = B(x)의 i차 항의 계수
*/
vector<cpx> mult(vector<cpx> &a, vector<cpx> &b) {
	int N = 1;
	
	//두 다항식의 곱의 degree는 degree(a) + degree(b)다.
 	//2의 제곱수가 되도록 크기를 맞춰준다
	while (N < a.size() + b.size()) N <<= 1;
	
	a.resize(N); 
	b.resize(N);

	// principal nth root of unity
	double angle = 2 * PI / N;
	cpx omega_n = cpx(cos(angle), sin(angle));

	//두 계수 표현을 p-v 표현으로 변환한다
	FFT(a, omega_n);
	FFT(b, omega_n);

	//각 점에 대해 두 다항식의 곱의 값을 구한다.
	vector<cpx> res(N);
	for (int i = 0; i < N; i++) res[i] = a[i] * b[i];

	//역변환. 
	omega_n = cpx(cos(-angle), sin(-angle));
	FFT(res, omega_n);
	for (int i = 0; i < N; i++) res[i] /= N;

	return res;
}

1개의 댓글

comment-user-thumbnail
2024년 8월 19일

푸리에는 빠른 연생일까요

답글 달기