https://www.acmicpc.net/problem/24930
24930번: Ordinary Ordinals
Sets, sets, sets. Everything in math is just a set. Even the natural numbers can be represented as sets. For example, we can represent the the number $0$ as the empty set $\{\}$. The number $1$ can be represented as $\{\{\}\}$. But what about $2$? Conside
www.acmicpc.net
폰 노이만이 자연수를 정의한 것을 차용한 문제입니다.
$N$에 대응되는 괄호 문자열의 길이를 $a_{N}$라고 하면, $N \geq 1$에 대해 다음이 성립함을 알 수 있습니다.
$$ a_{N} = a_{0} + a_{1} + a_{2} + \cdots + a_{N-1} + 2 + N-1 $$
위 식에서 $2$ 는 가장 바깥쪽의 괄호쌍, $N-1$ 은 콤마의 개수입니다.
이를 그대로 구현할 경우 $O \left ( N^{2} \right )$이라서, 약간의 식 변형을 통해 좀 더 간소한 점화식을 얻어보겠습니다.
$$ a_{N+1} = a_{0} + a_{1} + a_{2} + \cdots + a_{N-1} + a_{N} + 2 + N $$
$$ \therefore a_{N+1} - a_{N} = a_{N} + 1 \rightarrow a_{N+1} = 2a_{N} + 1$$
위 식은 $N \geq 1$에서 성립하고, 문제에서 주어진 대로 $a_{0}=2$ , $a_{1}=4$ 입니다.
이제 복잡도는 $O \left ( N \right )$으로 줄었지만, 여기서 복잡도를 $O \left ( \log N \right )$으로 줄이는 방법이 2가지 있습니다.
첫번째 방법은 일반항을 구하는 것입니다.
수열 $\left \{ b_{n} \right \} = a_{n} + 1$ 을 도입하여 다음을 알 수 있습니다.
$$ b_{N+1} = 2b_{N} $$
정의에 따라 $b_{1}=5$ 이므로, 수열의 일반항 $b_{N}=5 \times 2^{N-1}$ 을 얻습니다. ($N \geq 1$)
결과적으로 $a_{N} = 5 \times 2^{N-1} - 1$ 입니다. ($N \geq 1$)
두번째 방법은 행렬 점화식을 구하는 것입니다.
위에서 얻은 식을 그대로 활용하여 다음을 얻을 수 있습니다.
$$ \begin{bmatrix} a_{N+1} \\ a_{N} \\ 1 \end{bmatrix} = \begin{bmatrix} 2 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} a_{N} \\ a_{N-1} \\ 1 \end{bmatrix} $$
위 식은 $N \geq 1$에 대해 성립합니다. 즉, $N \geq 2$에 대해 다음이 성립합니다.
$$ \begin{bmatrix} a_{N} \\ a_{N-1} \\ 1 \end{bmatrix} = \begin{bmatrix} 2 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 0 & 1 \end{bmatrix}^{N-1} \begin{bmatrix} a_{1} \\ a_{0} \\ 1 \end{bmatrix} = \begin{bmatrix} 2 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 0 & 1 \end{bmatrix}^{N-1} \begin{bmatrix} 4 \\ 2 \\ 1 \end{bmatrix} $$
둘 다 올바른 방법이므로 어느쪽을 쓰든 상관 없지만, 만약 제한 시간이 더 짧아지거나, $M$이 더욱 커진다면 저는 첫번째 방법을 썼을 것 같습니다.
$M$이 $10^{18}$ scale로 커진다면 C 특성상 로그 시간 곱셈까지 동원하게 됩니다.
이 경우 곱셈 횟수가 행렬 곱셈을 활용하는 두 번째 방법이 월등히 크기 때문에, 실행 시간 차이가 커질 거라는 게 제 생각입니다.
그러나 $M < 2^{31}$ 이므로, $2^{63}$ scale의 $N$으로는 두 방법의 실행 시간 차이가 크진 않을 것 같습니다.
아래는 두 번째 방법으로 구현한 제 코드입니다.
#include <stdio.h>
typedef struct {
long long array[3][3];
} Matrix;
Matrix matrix_multiply_modular (Matrix A, Matrix B, long long M);
Matrix matrix_power_modular (Matrix A, long long P, long long M);
long long simple[4] = {0, 1, 2, 4};
int main() {
long long N, M;
scanf("%lld %lld", &N, &M);
if (N == 0)
{
printf("%d", 2 % M);
return 0;
}
else if (N == 1)
{
printf("%d", 4 % M);
return 0;
}
Matrix Base =
{{
{ 2, 0, 1 },
{ 1, 0, 0 },
{ 0, 0, 1 }
}};
Base = matrix_power_modular(Base, N - 1, M);
long long answer = 0;
for (int i = 0; i < 3; i++)
{
answer = (answer + Base.array[0][i] * simple[3 - i]) % M;
}
printf("%lld", answer);
return 0;
}
Matrix matrix_multiply_modular (Matrix A, Matrix B, long long M)
{
Matrix result = { {{0}} };
for (int i = 0; i < 3; i++)
{
for (int k = 0; k < 3; k++)
{
for (int j = 0; j < 3; j++)
{
result.array[i][j] = (result.array[i][j] + A.array[i][k] * B.array[k][j]) % M;
}
}
}
return result;
}
Matrix matrix_power_modular (Matrix A, long long P, long long M)
{
Matrix result = { {{0}} };
for (int i = 0; i < 3; i++)
{
result.array[i][i] = 1;
}
while(P > 0)
{
if (P % 2)
{
result = matrix_multiply_modular (result, A, M);
}
A = matrix_multiply_modular (A, A, M);
P /= 2;
}
return result;
}
(C11, 0ms, 1116KB, 제출번호 53407781)
위 수열은 OEIS에 등재되어 있습니다. http://oeis.org/A153894
'분할 정복을 이용한 거듭제곱 > 행렬 거듭제곱' 카테고리의 다른 글
백준 14553번: The Way (0) | 2023.01.17 |
---|---|
백준 13380번: Found (0) | 2023.01.16 |
백준 13630번: Buses (1) | 2023.01.08 |
백준 25962번: 선우의 셋리스트 (1) | 2023.01.08 |
백준 11333번: 4×n 타일링 (0) | 2022.05.01 |