.
문제 설명
가로 길이가 2이고 세로의 길이가 1인 직사각형 모양의 타일이 있습니다. 이 직사각형 타일을 이용하여 세로의 길이가 3이고 가로의 길이가 n인 바닥을 가득 채우려고 합니다. 타일을 채울 때는 다음과 같이 2가지 방법이 있습니다
- 타일을 가로로 배치 하는 경우
- 타일을 세로로 배치 하는 경우
예를들어서 n이 8인 직사각형은 다음과 같이 채울 수 있습니다.
직사각형의 가로의 길이 n이 매개변수로 주어질 때, 이 직사각형을 채우는 방법의 수를 return 하는 solution 함수를 완성해주세요.
https://school.programmers.co.kr/learn/courses/30/lessons/12902
제한 사항
- 가로의 길이 n은 5,000이하의 자연수 입니다.
- 경우의 수가 많아 질 수 있으므로, 경우의 수를 1,000,000,007으로 나눈 나머지를 return해주세요.
풀이
2x1, 1x2타일로 만들 수 있는 최소 길이 n은 2이다.
2를 만들 수 있는 경우의 수는 총 3가지이다.
이를 기반으로 dp를 진행하면서 문제를 풀 수 있다.
하지만, 다른 접근법으로 보다 효율적으로 문제를 풀 수 있다.
문제에서 주어진 타일을 한 칸으로 나눈다고 생각하면 총 4가지가 된다.
이를 이용하여 한 행씩 만들면서 가능한 경우를 세는 것이다.
한 행을 만드는데 $ 4^m $가지의 경우가 생긴다.
이를 n개의 행을 만들어야 하기 때문에 시간 복잡도는 O($ n4^m $)이다.
행을 만들면서 가능 여부를 판단해야 한다.
예를 들면, ∩ 밑에는 ∪이 와야 한다. 그렇지 않으면 가능한 경우가 아니다.
즉, $4^m$개의 행에서 $ 4^m $의 윗 행을 판단해야 하기 때문에 최종적인 시간복잡도는 O($ n4^{2m} $)가 된다.
첫 행의 가능한 경우의 수는 정해져 있다.
⊂, ⊃은 붙어 있어야 하며, ∪는 올 수 없다.
따라서, 이차원 배열을 통해 각 행에 대해 $4^m$의 가능한 경우의 수를 세면 된다.
첫 행은 가능한 경우의 수를 1로 초기화한다.
이후 행은 윗 행과 비교하여 가능한 경우라면 이차원 배열에 기록된 수를 가져와 더하면 된다.
즉, DP를 이용하여 문제를 풀 수 있다.
여기서 최적화할 수 있는 방법이 있다.
위에서 말했듯이 O($n4^{2m}$)의 시간 복잡도를 갖기 때문에 m이 작을수록 유리하다.
따라서, 타일을 90도 회전시키면 가로길이 m이 3으로 고정된다.
또한, 윗 행에 관련 있는 타일은 ∩밖에 없다.
즉, 타일의 종류를 ∩와 □로 줄일 수 있다. □는 ⊂ ⊃ ∪를 대치하는 것이다.
그렇게 한다면 시간 복잡도는 O($n2^6$)이 된다.
이렇게 최적화한다면 윗 행의 가능 여부를 판단하기 쉬워진다.
기본적으로 □의 위에는 ∩, ∩위에는 □가 무조건 올 수 있다.
하지만, 예외적으로 생각할 것이 있다.
□가 서로 붙어있는 경우는 ⊂ ⊃이고, □가 떨어져 있는 경우가 있다.
□가 떨어진 경우는 윗 행이 ∩가 되는 경우만 가능하다.
예를 들어 아래 행의 □ ∩ □이라고 했을 때, 위의 행은 ∩ □ ∩만 가능한 경우가 된다.
□가 붙어 있는 경우는 윗 행이 ∩가 되어도 되지만 붙어있는 □도 된다.
예를 들면, □ □ ∩의 윗 행은 ∩ ∩ □ 도 가능하며, □ □ □도 가능하다.
마지막으로 □로만 구성된 행이 있다.
□의 윗 행에는 ∩로만 구성된 행도 가능하며, □ □ ∩, ∩ □ □ 모두 가능하다.
□를 0, ∩을 1이라고 가정했을 때, 한 행의 가능한 경우의 수는 다음과 같다.
000 | 001 | 010 | 011 | 100 | 101 | 110 | 111 |
이를 배열의 인덱스로 표현할 수 있다.
따라서, 이차원 배열의 열 인덱스는 한 행의 가능한 조합을 나타내며, 각 행은 k번째 행을 나타내게 된다.
그리고 이차원 배열의 저장되는 값은 k번째 행이 해당하는 열이 배치되었을 때 가능한 경우가 된다.
이차원 배열을 채우는 예시는 다음과 같다.
000 | 001 | 010 | 011 | 100 | 101 | 110 | 111 | |
1 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 1 |
2 | 1+1+1 | 0+0 | 0 | 1 | 0+0 | 0 | 1 | 0 |
전체 코드
#include <string>
#include <vector>
#include <iostream>
using namespace std;
int solution(int n) {
int answer = 0;
int div = 1'000'000'007;
//0: l,r,u, 1: ⨅
vector<vector<long long>> tile(n+1, vector<long long>(8, 0));
//1줄은 001, 100, 111만 가능
tile[1][1] = 1;
tile[1][4] = 1;
tile[1][7] = 1;
//2줄부터 가능한 수 세기
for(int i = 2; i <= n; i++)
{
//가능한 경우의 수 8개
for(int j = 0; j < 8; j++)
{
// 000 -> 100, 001을 윗 행으로 가질 수 있음
if(j == 0)
{
tile[i][j] += tile[i-1][1] % div;
tile[i][j] += tile[i-1][4] % div;
}
// 100, 001 -> 000을 윗 행으로 가질 수 있음
if(j == 1 || j == 4)
{
tile[i][j] += tile[i-1][0] % div;
}
// 반전시킨 경우는 모든 조합에서 가능
tile[i][j] += tile[i-1][7^j] % div;
}
}
return tile[n][0] % div;
}