DP를 푸는 과정

 

1. 테이블 정의하기

2. 점화식 찾기

3. 초기값 정하기

 

코딩테스트에 나올 수준의 DP는 일단 점화식만 찾고나면 그 뒤는 초기값을 채워넣은 후에 반복문을 돌면서 배열을 채워넣으면 끝이다.

하지만 그 점화식을 찾는게 가장 어렵다.

주어진 문제가 DP로 풀 수 있는 문제라는 것 자체를 눈치채지 못할 수도 있다.

이 부분은 그저 많이 풀면서 테이블을 잡고 식을 세우는 과정을 반복학습 하는 수 밖에 없다.

 

연습 문제

 

1463: 1로 만들기

더보기

BFS로 구현할수도 있다. 하지만 DP를 이용하면 더 짧막하게 구현할 수 있다.

 

1. 테이블 정의하기

D[i] = i를 1로 만들기 위해 필요한 연산 사용 횟수의 최소값

 

2. 점화식 찾기

예를들어 D[12] 를 찾는다고 하면 총 3가지의 행동을 할 수 있다.

  • 3으로 나누거나 (D[12] = D[4] + 1)
  • 2로 나누거나 (D[12] = D[6] + 1)
  • 1을 빼거나 (D[12] = D[11] + 1)

 

최소값을 찾는것이기 때문에 D[12] = min(D[4]+1, D[6]+1, D[11]+1)이 성립하게 된다.

즉, D[K] = min(D[K/3]+1, D[K/2]+1, D[K-1]+1) 이다.

 

3. 초기값 정의하기

D[1] = 0

 

#include <iostream>
#include <algorithm>

using namespace std;

int arr[1000001];

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int n;
	cin >> n;

	for (int i = 2; i <= n; ++i)
	{
		arr[i] = arr[i - 1] + 1;
		if (i % 2 == 0) arr[i] = min(arr[i], arr[i / 2] + 1);
		if (i % 3 == 0) arr[i] = min(arr[i], arr[i / 3] + 1);
	}

	cout << arr[n];

	return 0;
}

 

9095: 1, 2, 3 더하기

더보기

1. 테이블 정의하기

D[i] = i를 1, 2, 3의 합으로 나타내는 방법의 수

 

2. 점화식 찾기

D[4]인 경우,

1+1+1+1, 3+1, 2+1+1, 1+2+1 / (3을 1, 2, 3의 합으로 나타내는 방법) + 1, D[3]

1+1+2, 2+2 / (2를 1, 2, 3의 합으로 나타내는 방법) + 2, D[2]

1+3 / (1을 1, 2, 3의 합으로 나타내는 방법) + 3, D[1]

D[4] = D[1] + D[2] + D[3]

D[i] = D[i-1] + D[i-2] + D[i-3]

 

3. 초기값 정의하기

D[1] = 1, D[2] = 2, D[3] = 4

초기값이 3개는 주어져야 한다.

 

#include <iostream>

using namespace std;

int arr[12];

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int tc, n;
	cin >> tc;

	// 미리 테이블을 구해두고 바로 출력해준다
	arr[1] = 1;
	arr[2] = 2;
	arr[3] = 4;

	for (int i = 4; i <= 11; ++i)
		arr[i] = arr[i - 3] + arr[i - 2] + arr[i - 1];

	while (tc--)
	{
		cin >> n;
		cout << arr[n] << '\n';
	}

	return 0;
}

 

2579: 계단 오르기

더보기

1. 테이블 정의하기

(X) D[i] = i번째 계단까지 올라섰을 때 점수 합의 최대값 (연속해서 3개의 계단을 밟을수 없다는 조건때문에 사용할 수 없음)

(O) D[i][j] = 현재까지 j개의 계단을 연속해서 밟고 i번째 계단까지 올라섰을 때 점수 합의 최대값. i번째 계단은 반드시 밟아야 함

 

2. 점화식 찾기

  • D[k][1] = max(D[k-2][1], D[k-2][2]) + S[k]
  • D[k[2] = D[k-1][1] + S[k]

 

3. 초기값 정의하기

D[1][1] = S[1], D[1][2] = 0

D[2][1] = S[2], D[2][2] = S[1] + S[2]

 

#include <iostream>

using namespace std;

int arr[301];
int psum[301][3];

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int n;
	cin >> n;

	for (int i = 1; i <= n; ++i)
		cin >> arr[i];

	psum[1][1] = arr[1];
	psum[1][2] = 0; // 현재 계단은 한개밖에 없다
	psum[2][1] = arr[2]; // 2층까지 한개를 연속해서 밟았다면 k-1개는 건너뛰었다는 뜻
	psum[2][2] = arr[1] + arr[2]; // 2층까지 두개를 연속해서 밟았다면 k-1, k 둘 다 밟았다는 뜻

	for (int i = 3; i <= n; ++i)
	{
		// i번째 계단이 연속해서 한개째 밟고 있다면 i-1번째 계단은 건너뛰고 i-2번째 계단을 밟았다는 뜻
		psum[i][1] = max(psum[i - 2][1], psum[i - 2][2]) + arr[i];
		// i번째 계단이 연속해서 두개째 밟고있는 계단이라면 i-1번째 계단을 밟았다는 뜻. 연속으로 3개는 밟지 못하므로 i-2 또는 i-1의 연속2개는 안된다
		psum[i][2] = psum[i - 1][1] + arr[i];
	}

	cout << max(psum[n][1], psum[n][2]);

	return 0;
}

 

1149: RGB거리

더보기

1. 테이블 정의하기

D[i][0] = i번째 집까지 칠할 때 비용의 최소값, i번째 집은 빨강

D[i][1] = i번째 집까지 칠할 때 비용의 최소값, i번째 집은 초록

D[i][2] = i번째 집까지 칠할 때 비용의 최소값, i번째 집은 파랑

 

2. 점화식 찾기

  • D[i][0] = min(D[i-1][1], D[i-1][2]) + R[i]
  • D[i][1] = min(D[i-1][0], D[i-1][2]) + G[i]
  • D[i][2] = min(D[i-1][0], D[i-1][1]) + B[i]

 

3. 초기값 정의하기

D[1][0] = R[1]

D[1][1] = G[1]

D[1][2] = B[1]

 

최종적으로 D[N]중 가장 작은값을 출력하면 된다.

 

#include <iostream>
#include <algorithm>

using namespace std;

int arr[1001][3];
int r[1001], g[1001], b[1001];

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int n, answer = 0;
	cin >> n;

	for (int i = 1; i <= n; ++i)
	{
		cin >> r[i] >> g[i] >> b[i];
	}

	arr[1][0] = r[1];
	arr[1][1] = g[1];
	arr[1][2] = b[1];

	for (int i = 2; i <= n; ++i)
	{
		arr[i][0] = min(arr[i-1][1], arr[i-1][2]) + r[i];
		arr[i][1] = min(arr[i-1][0], arr[i-1][2]) + g[i];
		arr[i][2] = min(arr[i-1][0], arr[i-1][1]) + b[i];
	}

	cout << *min_element(arr[n], arr[n]+3);

	return 0;
}

 

11726: 2×n 타일링

더보기

1. 테이블 정의하기

D[i] = 2×i 크기의 직사각형을 채우는 방법의 수

 

2. 점화식 찾기

  • 2×1 크기의 타일을 하나 놓았을 때 나머지 타일을 채우는 방법의 수는 D[i-1]
  • 1×2 크기의 타일을 하나 놓았을 때, 아래쪽 공간에는 똑같은 1×2 타일만 들어갈 수 있다. 이 때, 나머지 타일을 채우는 방법의 수는 D[i-2]

D[i] = D[i-1] + D[i-2]

 

3. 초기값 정의하기

D[1] = 1, D[2] = 2

 

#include <iostream>

using namespace std;

int arr[1001];
int mod = 10007;

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int n;
	cin >> n;

	arr[1] = 1;
	arr[2] = 2;

	for (int i = 3; i <= n; ++i)
		arr[i] = (arr[i - 1] + arr[i - 2]) % mod; // 매번 mod의 나머지값만 가져가면 된다

	cout << arr[n];

	return 0;
}

 

11659: 구간 합 구하기 4

더보기

1. 테이블 정의하기

D[i] = A[1] + A[2] + ... + A[i] = D[i-1] + A[i]

 

2. 점화식 찾기

  • D[j] - D[i-1] = (A[1] + A[2] + .. A[j]) - (A[1] + A[2] + .. + A[i-1)

 

3. 초기값 정의하기

D[0] = 0

 

#include <iostream>

using namespace std;

int arr[100001];
int prefixSum[100001];

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int n, m;
	cin >> n >> m;

	for (int i = 1; i <= n; ++i)
	{
		cin >> arr[i];
		prefixSum[i] = prefixSum[i - 1] + arr[i];
	}

	while (m--)
	{
		int i, j;
		cin >> i >> j;
		cout << prefixSum[j] - prefixSum[i - 1] << '\n';
	}

	return 0;
}

 

 

기본 문제

 

1003: 피보나치 함수

더보기
#include <bits/stdc++.h>
using namespace std;

vector<pair<int, int>>v(41);

int main(void)
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int tc;
	cin >> tc;
	v[0] = { 1, 0 };
	v[1] = { 0, 1 };
	for (int i = 2; i <= 40; ++i)
		v[i] = { v[i - 1].first + v[i - 2].first, v[i - 1].second + v[i - 2].second };
	while (tc--)
	{
		int n;
		cin >> n;
		cout << v[n].first << ' ' << v[n].second << '\n';
	}

	return 0;
}

 

1932: 정수 삼각형

더보기
#include <bits/stdc++.h>
using namespace std;

int t[501][501];
int n;

int main(void)
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	cin >> n;
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 1; j <= i; ++j)
			cin >> t[i][j];
	}

	for (int i = n-1; i > 0; --i)
	{
		for (int j = 1; j <= i; ++j)
			t[i][j] = max(t[i + 1][j], t[i + 1][j + 1]) + t[i][j];
	}
	cout << t[1][1];
	
	return 0;
}

 

비재귀 top-down으로 깔끔하게 구현이 됨

처음에는 bottom-down으로 하려다가 도무지 답이 안나와서 구글링의 도움을 받았다

 

11727: 2×n 타일링 2

더보기
#include <bits/stdc++.h>
using namespace std;

int n, mod = 10007;
int t[1001];

int main(void)
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	cin >> n;
	
	t[1] = 1;
	t[2] = 3;
	for (int i = 3; i <= n; ++i)
		t[i] = (t[i - 1] + t[i - 2] * 2) % mod;
	cout << t[n];

	return 0;
}

 

2193: 이친수

더보기
using namespace std;
using ll = long long;
int n;
ll t[91];

int main(void)
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	cin >> n;
	t[0] = 1; t[1] = 1; t[2] = 1;
	for (int i = 3; i <= n; ++i)
	{
		for (int j = 0; j <= i - 2; ++j) t[i] += t[j];
	}
	cout << t[n];

	return 0;
}

 

나름 자력으로 풀었음

 

1912: 연속합

더보기
#include <bits/stdc++.h>
using namespace std;
int n, mx, ans;
int t[100001];

int main(void)
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin >> n;
	for (int i = 0; i < n; ++i) cin >> t[i];

	mx = ans = t[0];
	for (int i = 1; i < n; ++i)
	{
		mx = max(t[i], t[i] + mx);
		ans = max(ans, mx);
	}
	cout << ans;
	return 0;
}

 

절반정도는 접근했다가 생각의 늪에 빠져서 못풀고 구글링했음

최대값들 중의 최대값을 선별해야됨

 

'알고리즘 > 바킹독의 실전 알고리즘' 카테고리의 다른 글

[0x12] 수학  (0) 2022.08.05
[0x11] 그리디  (0) 2022.08.04
[0x0F] 정렬 II  (0) 2022.08.02
[0x0E] 정렬 I  (0) 2022.08.02
[0x0D] 시뮬레이션  (0) 2022.08.01

+ Recent posts