티스토리 뷰

공부

선형점화식 빠르게 계산하기

전명우 2022. 11. 30. 10:14

$D_0, D_1, \cdots, D_{k-1}$과 $c_1, c_2, \cdots, c_k$가 주어졌을 때, $i \ge k$인 $D_i$를 다음과 같은 선형점화식으로 구할 수 있다고 하자.

$$D_i = \sum_{j=1}^{k}{c_jD_{i-j}} = c_1D_{i-1} + c_2D_{i-2} + \cdots + c_kD_{i-k}$$

이러한 선형점화식을 가지는 가장 유명한 예시는 피보나치 수열이다. 피보나치 수열은 위 식에서 $k=2$, $D_0 = 1$, $D_1 = 1$, $c_1 = 1$, $c_2 = 2$이다.

이러한 선형점화식이 주어졌을 때, $D_N$을 빠르게 구하는 방법을 알아보자.

 

1) 행렬 곱셈을 이용한 방법

$$\begin{pmatrix}
D_N \\
D_{N-1}\\
D_{N-2}\\
\vdots \\
D_{N-k+1}
\end{pmatrix}
=
\begin{pmatrix}
c_1 & c_2 & \cdots & c_{k-1} & c_{k} \\
1 & 0 & \cdots & 0 & 0 \\
0 & 1 & \cdots & 0 & 0\\
\vdots & \vdots & \ddots & \vdots & \vdots  \\
0 & 0 & \cdots & 1 & 0
\end{pmatrix}
\begin{pmatrix}
D_{N-1} \\ D_{N-2} \\ D_{N-3} \\ \vdots \\ D_{N-k}
\end{pmatrix}$$

주어진 선형점화식을 위와 같이 행렬식으로 표현할 수 있다. 이 $k \times k$ 크기의 행렬을 $A$라고 하자. 이는 다음과 같이 거듭제곱을 이용하여 표현할 수 있다.

$$\begin{pmatrix}
D_N \\
D_{N-1}\\
D_{N-2}\\
\vdots \\
D_{N-k+1}
\end{pmatrix}
=
\begin{pmatrix}
c_1 & c_2 & \cdots & c_{k-1} & c_{k} \\
1 & 0 & \cdots & 0 & 0 \\
0 & 1 & \cdots & 0 & 0\\
\vdots & \vdots & \ddots & \vdots & \vdots  \\
0 & 0 & \cdots & 1 & 0
\end{pmatrix}^{N-k+1}
\begin{pmatrix}
D_{k-1} \\ D_{k-2} \\ D_{k-3} \\ \vdots \\ D_{0}
\end{pmatrix} = A^{N-k+1}
\begin{pmatrix}
D_{k-1} \\ D_{k-2} \\ D_{k-3} \\ \vdots \\ D_{0}
\end{pmatrix}$$

즉, 정수 $a$의 $a^N$을 분할정복을 통해 $O(\lg N)$ 시간복잡도에 구할 수 있다. 이와 같은 방법으로 크기 $k \times k$인 행렬 $A$의 $A^N$을 $O(k^3 \lg N)$ 시간복잡도에 구할 수 있다. 이렇게 $A^{N-k+1}$을 구하면 $D_N$을 계산할 수 있다.

 

2) 키타마사법

만약 $k$가 크다면 $O(k^3 \lg n)$ 시간복잡도는 느릴 것이다. 좀 더 빠르게 구하는 방법을 예시를 통해 알아보자.

$k=2$이고, 점화식은 $D_i = 2D_{i-1}+3D_{i-2}$이다. $D_5$를 구하는 과정을 살펴보자. 이제 $D_5 = aD_1 + bD_0$ 꼴로 표현해보자.

$$D_5 = 2D_4+3D_3 = 2(2D_3+3D_2)+3D_3\\ = 7D_3+6D_2 = 7(2D_2+3D_1)+6D_2\\ = 20D_2 + 21D_1 = 20(2D_1+3D_0)+21D_1\\ = 61D_1+60D_0$$

$D_1$과 $D_0$을 알고 있기 때문에 $D_5$를 바로 계산할 수 있다.

 

점화식을 다항식으로 표현해보자. $x^i = 2x^{i-1}+3x^{i-2}$이며, $x^2 = 2x + 3$, 즉, $x^2-2x-3 = 0$이다. 다항식 $x^N$를 $x^2-2x-3$으로 나눠보면 다음과 같이 표현할 수 있다.

$$x^N = Q(x^2-2x-3) + R$$

다항식 $Q$는 나눗셈의 몫이 되며, 다항식 $R$은 나머지가 된다. 그리고 다항식 $R$의 차수는 2보다 작다. 만약, 다항식 $x^5$을 $x^2-2x-3$으로 나눠보면 다음과 같이 표현된다.

$$x^5 = (x^3+2x^2+7x+20)(x^2-2x-3) + 61x + 60$$

즉, 다항식 $R$의 계수가 $D_N = aD_1 + bD_0$ 꼴에서 $a$와 $b$가 된다.

 

일반화하면 점화식을 $g(x) = x^k - c_1x^{k-1} - \cdots - c_{k-1}x - c_k = 0$으로 나타내고 $x^N$을 $g(x)$로 나눈 나머지를 구하면 $D_N$을 구할 수 있다.

$$x^N = 
\begin{cases}
x^{\lfloor\frac{N}{2}\rfloor}x^{\lfloor\frac{N}{2}\rfloor}x & \mod g(x), & \text{if } N \text{ is odd}\\
x^{\frac{N}{2}}x^{\frac{N}{2}} & \mod g(x), & \text{otherwise}
\end{cases}$$

다항식의 경우에도 위 식이 성립하므로 $x^N$을 $g(x)$로 나눈 나머지를 $O(\lg N)$번의 곱셈과 나눗셈으로 구할 수 있다. 매 순간 $g(x)$로 나눈 나머지만 가지고 있으면 되므로, 곱하고 나누는 다항식의 차수는 $k$ 보다 작다.

 

차수가 $k$인 다항식의 곱셈과 나눗셈을 $O(k^2)$ 시간복잡도에 할 수 있는데, 이 경우 전체 시간복잡도는 $O(k^2 \lg N)$이 된다. 1번 방법과 비교했을 때 $k$ 배 빠른 방법이다.

 

3) 키타마사법 + NTT + 빠른 다항식 나눗셈

2번 방법에서 다항식의 곱셈과 다항식의 나눗셈에 $O(k^2)$ 시간이 필요했으므로 전체 시간복잡도가 $O(k^2 \lg N)$이었다.

 

만약, 문제의 MOD 값이 $a2^b+1$ 꼴의 소수이며 원시근을 알고 $k < 2^b$라면 NTT가 가능하다. 다항식의 곱셈은 NTT 그 자체로 $O(k \lg k)$ 시간에 가능하며, 나눗셈 또한 이 글에 있는 방법을 통해 $O(k \lg k)$에 가능하다.

 

전체 시간복잡도는 $O(k \lg k \lg N)$이 된다.

 

4) Bostan Mori 알고리즘

Bostan Mori 알고리즘에 대한 자세한 설명은 이 글을 참고하자.

 

이 알고리즘 또한 NTT를 필요로 하므로 3번 방법과 사용 가능한 조건이 동일하다. 시간복잡도 또한 $O(k \lg k \lg N)$으로 3번 방법과 동일하다. 그러나 다항식의 나눗셈을 필요로 하지 않는다는 장점이 있다. 다항식의 나눗셈의 경우, 시간복잡도는 $O(k \lg k)$이지만, 상수가 크고 별도로 구현해야 한다는 번거로움이 있기 때문에 이 알고리즘은 구현이 상대적으로 편하며 실행 시간이 3번 방법보다 빠르다.

 

*) 예시 문제

1번 방법의 기본 연습 문제다. 

 

2749번: 피보나치 수 3

첫째 줄에 n이 주어진다. n은 1,000,000,000,000,000,000보다 작거나 같은 자연수이다.

www.acmicpc.net

MOD 값이 $1999 = 999\times2^1+1$이기 때문에 NTT를 쓸 수 없다. 2번 방법, $O(k^2 \lg N)$으로 구현해야 한다.

 

15572번: 블록 4

여러 가지 블록들을 이용하여 직사각형 모양을 만들려고 한다. 우리에게는 1 × N 블록, 2 × N 블록, ..., N × N 블록이 무한하게 있다. 이 블록들을 사용하여 N × M 모양을 만들고

www.acmicpc.net

MOD 값이 $104857601 = 25 \times 2^{22}+1$ 로 NTT를 쓸 수 있다. 원시근은 2부터 차례대로 구해보면 되는데, 3이 원시근이 된다. $k$의 최댓값이 30000으로 크기 때문에 1번, 2번 방법으로 풀 수 없다.

 

13725번: RNG

첫째 줄에 k와 N (1 ≤ k ≤ 30,000, 1 ≤ N ≤ 1018)이 주어진다. 둘째 줄에는 A1, A2, ..., Ak가 셋째 줄에는 C1, C2, ..., Ck가 주어진다. (0 ≤ Ai, Ci < 104857601)

www.acmicpc.net

아래 코드는 BOJ 13725 RNG 문제를 해결하는 코드이며, mod int와 NTT를 이용한 다항식 템플릿을 포함한 코드다.

더보기

'공부' 카테고리의 다른 글

Bostan Mori 알고리즘  (0) 2022.11.30
다항식 나눗셈의 몫을 빠르게 구하는 방법  (0) 2022.11.29
Hu-Tucker Algorithm  (0) 2022.05.03
Skew Heap  (0) 2022.05.03
Z-function  (2) 2021.03.08
댓글
댓글쓰기 폼