BOJ 4883 - 삼각 그래프

“다이나믹 프로그래밍”
BOJ 4883 - 삼각 그래프
문제 링크


#include <bits/stdc++.h>
#include <cmath>
#define all(x) (x).begin(), (x).end()
#define rep(i, a, b) for (int i = (a); i < (b); ++i)
const int INF = 0x3f3f3f3f;
using namespace std;
int N;
int board[100010][4];
int dp[100010][4];
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(NULL); cout.tie(NULL);
int test = 1;
while (1) {
cin >> N;
if (N == 0)
return 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < 3; j++)
cin >> board[i][j];
}
dp[0][0] = INF;
dp[0][1] = board[0][1];
dp[0][2] = dp[0][1] + board[0][2];
dp[1][0] = board[0][1] + board[1][0];
dp[1][1] = min({dp[1][0] + board[1][1], dp[0][1] + board[1][1], dp[0][2] + board[1][1]});
dp[1][2] = min({dp[1][1] + board[1][2], dp[0][1] + board[1][2], dp[0][2] + board[1][2]});
for (int i = 2; i < N; i++) {
dp[i][0] = min(dp[i - 1][0] + board[i][0], dp[i - 1][1] + board[i][0]);
dp[i][1] = min({dp[i][0] + board[i][1], dp[i - 1][0] + board[i][1],
dp[i - 1][1] + board[i][1], dp[i - 1][2] + board[i][1]});
dp[i][2] = min({dp[i][1] + board[i][2], dp[i - 1][1] + board[i][2],
dp[i - 1][2] + board[i][2]});
}
cout << test << ". " << dp[N - 1][1] << '\n';
test += 1;
}
return 0;
}
다이나믹 프로그래밍 문제를 좀 풀어봤다면, 꽤나 익숙한 스타일의 클래식한 dp 문제라는 걸 알 수 있다. 이 문제도 마찬가지로 출발점으로부터 그래프를 내려오면서 해당 node에서의 최댓값을 memoization 하고 특정 도착 지점의 최댓값을 출력하는 그런 문제다.
그러나 나는 그래프를 제대로 쳐다보지도 않고 머릿속에 박혀있는 풀이를 그대로 발사했다가 큰 낭패를 봤다. 그래프의 간선이 이동하는 방향을 잘 살펴봐야한다. 일반적인 문제처럼 위에서 아래로만 이동할 수 있는게 아니라, 같은 층의 노드에서도 움직일 수가 있다.

- 예를 들어서 2번째 행의 2번째 열 값인
13
에 접근할 수 있는 경우의 수는 무엇이 있을까? 13에 접근하는 화살표의 수, 즉 4개이다.

내가 알았던 유형에서는 저 파란 원 위에 있는 즉, 같은 층의 노드에서 접근하는 경우가 존재하지 않았고, 계속해서 저 경우를 제외하고 점화식을 세워서 WA를 받았었다…
첫 번째 행과 두 번째 행은 직접 경우를 작성했고, 그 아래 행부터는 점화식을 작성해서 풀었다. 첫 번째 행의 경우 첫 번째 행 첫 번째 값은 애초에 접근할 일이 없기 때문에 INF로 기록해두었다. 이후부터는 큰 틀에서 화살표의 수를 따져가며 경우를 작성하면 된다.
- 첫 번째 행
dp[0][0] = INF; // 거쳐갈 일이 없는 점 dp[0][1] = board[0][1]; // 시작점 dp[0][2] = dp[0][1] + board[0][2]; // 시작점에서 바로 옆으로 가는 화살표 단 하나
- 두 번째 행
// dp[0][0] 이 INF이기 때문에, 그쪽에서 내려오는 화살표를 고려할 필요는 없다. 첫 값의 경우 화살표 1개 dp[1][0] = board[0][1] + board[1][0]; // 가운데 값의 경우 화살표 3개 dp[1][1] = min({ dp[1][0] + board[1][1], dp[0][1] + board[1][1], dp[0][2] + board[1][1] }); // 끝 값의 경우 화살표 3개 dp[1][2] = min({ dp[1][1] + board[1][2], dp[0][1] + board[1][2], dp[0][2] + board[1][2] });
그 이외의 행
for (int i = 2; i < N; i++)
{
// 첫 값의 경우 화살표 2개
dp[i][0] = min(dp[i - 1][0] + board[i][0], dp[i - 1][1] + board[i][0]);
// 가운데 값의 경우 화살표 4개
dp[i][1] = min({
dp[i][0] + board[i][1],
dp[i - 1][0] + board[i][1],
dp[i - 1][1] + board[i][1],
dp[i - 1][2] + board[i][1]
});
// 끝 값의 경우 화살표 3개
dp[i][2] = min({
dp[i][1] + board[i][2],
dp[i - 1][1] + board[i][2],
dp[i - 1][2] + board[i][2]
});
}