Processing math: 36%
본문 바로가기

분할 정복을 이용한 거듭제곱/행렬 거듭제곱

백준 24930번: Ordinary Ordinals

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에 대응되는 괄호 문자열의 길이를 aN라고 하면, N1에 대해 다음이 성립함을 알 수 있습니다.

 

aN=a0+a1+a2++aN1+2+N1

 

위 식에서 2 는 가장 바깥쪽의 괄호쌍, N1 은 콤마의 개수입니다.

 

이를 그대로 구현할 경우 O(N2)이라서, 약간의 식 변형을 통해 좀 더 간소한 점화식을 얻어보겠습니다.

 

aN+1=a0+a1+a2++aN1+aN+2+N

 

위 식은 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이 더욱 커진다면 저는 첫번째 방법을 썼을 것 같습니다.

 

M10^{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