포스트

백준 Bottom-UP DP 문제들 2

알고리즘 기초 문제 중 DP 기본 문제(400)

이 중 bottom-up 방식의 2차원 DP 문제들

1, 2, 3 더하기5

쉬운 계단 수

1. 1,2,3 더하기5

정수 N을 1,2,3의 합으로 나타내는 방법의 개수를 구하는 문제.

단, 같은 수를 두번 이상 연속해서 사용하면 안된다

1,2,3 더하기 문제의 업그레이드 버전

예시

일단 해당 수를 만들 수 있는 모든 경우의 수를 확인해보면 다음과 같다

1
2
3
4
1 = 1
2 = 1+1, 2
3 = 1+1+1, 1+2, 2+1, 3
4 = 1+1+1+1, 1+1+2, 1+2+1, 1+3, 2+2, 2+1+1, 3+1

다음과 같이 만들 수 있다

점화식

dp[i][j]를 i를 만드는데, j로 끝나는 방법의 개수라고 하면

1
2
3
4
5
6
7
8
9
dp[1][1] = 1; // 1을 만드는데, 1로 끝나는 방법 : 1개 (1)

dp[2][1] = 0; // 2를 만드는데, 1로 끝나는 방법 : 0개 (1+1은 불가능)
dp[2][2] = 1; // 2를 만드는데, 2로 끝나는 방법 : 1개 (2)

dp[3][1] = 1; // 3을 만드는데, 1로 끝나는 방법 : 1개 (1+1+1은 불가능, 2+1 가능)
dp[3][2] = 1; // 3을 만드는데, 2로 끝나는 방법 : 1개 (1+2)
dp[3][3] = 1; // 3을 만드는데, 3로 끝나는 방법 : 1개 (3)
...

로 볼 수 있다.

dp[i][1] = dp[i-1][1] + dp[i-1][2] + dp[i-1][3]인데, 같은 수를 두번 이상 연속해서 사용하면 안되므로

dp[i-1][1]를 제외한 dp[i][1] = dp[i-1][2] + dp[i-1][3] 로 나타낼 수 있다

마찬가지로 dp[i][2] = dp[i-2][1] + dp[i-2][3], dp[i][3] = dp[i-3][1] + dp[i-3][2] 로 정리할 수 있다

dp[N][1, 2, 3]을 각각 구해서 더해주면 된다

  • 주의! dp[i]를 더하는 과정에서 INT형을 초과할 수 있으므로,
    int보다 더 큰 자료형을 사용할것이 아니라면, 연산 시 MOD로 나눠주는 작업을 수행해주어야함

소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>

#define MOD 1000000009

using namespace std;

int T, N;
int dp[100001][4]; // N을 만드는데, 1, 2, 3으로 끝나는 방법
// MOD로 나눠주기때문에 overflow는 발생하지 않음 

void init() {
	/*
	중복을 포함해서 만들 수 있는 경우의 수 
	1 = 1
	2 = 1+1, 2
	3 = 1+1+1, 1+2, 2+1, 3
	4 = 1+1+1+1, 1+1+2, 1+2+1, 1+3, 2+2, 2+1+1, 3+1
	*/
	dp[1][1] = 1; // 1을 만드는데, 1로 끝나는 방법 : 1개 (1)
	dp[2][1] = 0; // 2를 만드는데, 1로 끝나는 방법 : 0개
	dp[2][2] = 1; // 2를 만드는데, 2로 끝나는 방법 : 1개 (2)
	dp[3][1] = 1; // 3을 만드는데, 1로 끝나는 방법 : 1개 (2+1)
	dp[3][2] = 1; // 3을 만드는데, 2로 끝나는 방법 : 1개 (1+2)
	dp[3][3] = 1; // 3을 만드는데, 3로 끝나는 방법 : 1개 (3)
	
	for (int i = 4; i <= 100000; i++) {
		dp[i][1] = (dp[i - 1][2] + dp[i - 1][3]) % MOD;
		dp[i][2] = (dp[i - 2][1] + dp[i - 2][3]) % MOD;
		dp[i][3] = (dp[i - 3][1] + dp[i - 3][2]) % MOD;
	}

}

int main() {

	init();

	cin >> T;
	while (T--) {
		cin >> N;
		cout << ((dp[N][1] + dp[N][2]) % MOD + dp[N][3]) % MOD << '\n';
		// 덧셈중 overflow가 발생할 수 있으므로, 두개씩 처리
	}

	return 0;
}

2. 쉬운 계단 수

45656과 같이, 인접한 모든 자리의 차이가 1인 수를 계단 수라고 한다.

N이 주어질 때, 길이가 N인 계단 수가 총 몇개있는지 구하는 문제

예시

한자리수는 모두 계단수로 취급한다

두자리수부터

1
2
3
4
5
6
7
8
9
10 12
21 23
32 34
43 45
54 56
65 67
76 78
87 89
98 

총 17개가 있다

이를 십의자리 수로 맞추는게 아닌, 일의자리수로 맞춰보면

1
2
3
4
5
6
7
8
9
10
(10)
(21)
(12, 32)
(23, 43)
(34, 54)
(45, 65)
(56, 76)
(67, 87)
(78, 98)
(89)

로 정렬할 수 있다

점화식

dp[i][j]를 i는 자리수, j는 마지막 자리수라고 하면 dp[2][1]은 1로 끝나는 2자리수의 개수라고 할 수 있다(1개)

1
2
3
4
5
6
7
8
9
10
dp[2][0] = 1 (10)
dp[2][1] = 1 (21)
dp[2][2] = 2 (12, 32)
dp[2][3] = 2 (23, 43)
dp[2][4] = 2 (34, 54)
dp[2][5] = 2 (45, 65)
dp[2][6] = 2 (56, 76)
dp[2][7] = 2 (67, 87)
dp[2][8] = 2 (78, 98)
dp[2][9] = 1 (89)

이를 통해 점화식을 뽑아내면

j로 끝나는 i자리수의 개수 = j-1로 끝나는 i-1자리수의 개수 + j+1로 끝나는 i-1자리수의 개수

dp[i][j] = dp[i-1][j-1] + dp[i+1][j+1] 로 표현할 수 있다

그런데 dp[i][0]dp[i-1][1]밖에 없고, dp[i][9] 또한, dp[i-1][8]밖에 없으므로 주의한다

소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>

#define MOD 1000000000

using namespace std;

int dp[101][10];

int main() {
	int N; cin >> N;

	// 1의 자리수는 모두 1
	for (int i = 1; i <= 9; i++) {
		dp[1][i] = 1;
	}

	for (int i = 2; i <= N; i++) {
		for (int j = 0; j <= 9; j++) {
			if (j == 0) {
				dp[i][0] = dp[i - 1][1];
			}
			else if (j == 9) {
				dp[i][9] = dp[i - 1][8];
			}
			else {
				dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j + 1]) % MOD;
			}
		}
	}
	int res = 0;
	for (int i = 0; i <= 9; i++) {
		res = (res + dp[N][i]) % MOD;
	}
	cout << res;

	return 0;
}

dp 문제는 항상 점화식을 뽑아내는게 가장 어렵다

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.