※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 13976번 문제인 타일 채우기 2이다.
문제는 아래 링크를 확인하자.

https://www.acmicpc.net/problem/13976 

 

13976번: 타일 채우기 2

첫째 줄에 N(1 ≤ N ≤ 1,000,000,000,000,000,000)이 주어진다.

www.acmicpc.net

N이 적당히 작다면 적당한 DP문제였겠지만, N이 1,000,000,000,000,000,000까지 입력이 들어오므로 일반적인 DP는 사용할 수 없을 것이다. 대신 이 문제는 점화식을 구해, 해당 점화관계를 행렬의 거듭제곱을 통해 O(lgN)의 속도로 계산하는 것으로 해결할 수 있다.

 

An을 N의 값이 n일 때의 답이라고 하자.

우선, N이 홀수라면 타일을 배치할 수 없으므로 0을 출력한다.

N이 0이라면 아무 타일도 배치하지 않는 하나의 경우의 수가 존재하므로 A0 = 1이다.

N이 2라면 (문제 예제에도 나와있듯이) A2 = 3이다.

 

N이 4 이상의 짝수라면, 두 경우를 제외하면 "전체 타일이 완전히 세로선으로 두 부분 타일링으로 나뉠 수 있는 곳" 중 가장 오른쪽 선이 존재할 것이고, 이를 바탕으로 식을 세우면 다음과 같은 식을 얻는다.

 

A_2k = 3 * A_2k-2 + 2 * A_2k-4 + 2 * A_2k-6 + ... + 2 * A0

 

여기서, A_2k와 A_2k-2를 나타내는 점화식의 차를 구하면 더 간단해진 형태의 점화식을 얻을 수 있다. 이는 직접 해보자.

 

위에서 얻은 점화식을 이용하여 행렬의 거듭제곱을 통한 빠른 계산을 해주자.

아래는 제출한 소스코드이다.

#include <iostream>
using namespace std;
typedef long long ll;

ll mat[2][2] = { {4,-1},{1,0} };
ll vec[2] = { 3,1 };

ll tmat[3][3];
ll tvec[3];

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);

	ll N; cin >> N;
	if (N & 1) cout << 0;
	else {
		N >>= 1;
		N--;
		while (N > 0) {
			if (N & 1) {
				for (int i = 0; i < 2; i++) {
					tvec[i] = 0;
					for (int k = 0; k < 2; k++) {
						tvec[i] += mat[i][k] * vec[k];
					}
				}
				for (int i = 0; i < 2; i++) {
					vec[i] = tvec[i] % 1000000007;
				}
			}

			for (int i = 0; i < 2; i++) {
				for (int j = 0; j < 2; j++) {
					tmat[i][j] = 0;
					for (int k = 0; k < 2; k++) {
						tmat[i][j] += mat[i][k] * mat[k][j];
					}
				}
			}
			for (int i = 0; i < 2; i++) {
				for (int j = 0; j < 2; j++) {
					mat[i][j] = tmat[i][j] % 1000000007;
				}
			}
			N >>= 1;
		}

		if (vec[0] < 0) vec[0] += 1000000007;
		cout << vec[0];
	}
}
728x90

'BOJ' 카테고리의 다른 글

[BOJ 14860 // C++] GCD 곱  (0) 2021.07.04
[BOJ 3088 // C++] 화분 부수기  (0) 2021.07.03
[BOJ 5032 // C++] 탄산 음료  (0) 2021.07.01
[BOJ 21955 // C++] Split  (0) 2021.07.01
[BOJ 3745 // C++] 오름세  (0) 2021.07.01

+ Recent posts