[백준 1520] 내리막 길

문제 보러가기

한번에 성공해서 기분이 좋다 ㅎ.

  • 참고
    • 경로는 정해져 있다. 사실상 단방향 Acyclic Graph 문제이다.

내가 푼 방법

Priority Queue + DP. 나는 순방향으로 (0,0)에서 시작해서 점진적으로 값이 업데이트 되는 방법을 사용했다. dp 배열의 의미는 다음과 같다.

dp[a][b] = “(0,0)부터 (a,b)까지 경로의 수”

이 배열을 (0,0)부터 시작해서 (M-1,N-1)까지 차례로 구해나가는 방법을 썼다.

중간 문제점

처음에는 Queue를 사용하려고 하였다. 이러면 문제가 발생했다. 분명 dp[a][b]는 (0,0)부터 (a,b)까지의 ‘최종적인’ 경로의 수를 담고 있어야 하는데, 단순히 큐를 이용하면 이 정보를 담지 못하게 되는 경우가 발생했다.

img

여기서 큐가 1 다음에 [3,2]순으로 되어 있다고 생각해보자. 3을 먼저 꺼내서 (그림에는 없지만) 인접 노드들에 대해서 경로를 업데이트한다. 그 다음에는 2를 꺼내서 인접 노드들을 업데이트한다. 문제는 2의 인접노드 중 하나가 3이므로, 3이 다시 큐에 푸쉬되어 동일한 연산을 반복한다는 것이다. 이 문제를 막기 위해서 priority queue를 사용했다. 2의 높이는 3의 높이보다 크다. priority queue는 노드의 높이 기준, 내림차순으로 설정했다. 이러면 [2,3]으로 설정되어 3 노드를 중복으로 방문하지 않게 된다.

다른 사람들 대부분의 풀이

DFS + DP

대부분의 풀이를 보니 (M-1,N-1)에서부터 시작해 (0,0)으로 (역방향으로) 퍼져나가는 방법을 사용했더라. 이게 조금 더 뭔가 안정적인 방법처럼 보이기는 했다. 이때의 dp 정의는 다음과 같다.

dp[a][b] = “(a,b)부터 (M-1,N-1)까지 경로의 개수”

dp 배열을 -1과 같은 값으로 초기화 해놓고

  • 초기값이면 DFS를 이용해 탐색
  • 이미 탐색했던 값이면 dp 배열 이용

이러한 식으로 계속 업데이트 해나가는 방식이다. 문제를 조금만 꼬았으면 내 방식으로는 못풀지 않았을까 싶긴 한 것 같다

코드

코드 보기
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/*
1520 : 내리막 길
DP 문제
*/

#include <iostream>
#include <vector>
#include <queue>
#define ull unsigned long long
#define pii pair<int, int>
using namespace std;

/*
경로의 수를 저장하는 DP 배열
dp[i][j] = (i,j)까지의 경로의 수를 나타낸다
*/
ull dp[600][600] = {
	0,
};
int M = 0, N = 0;
vector<vector<ull>> map;
vector<vector<bool>> visited;
struct comp
{
	bool operator()(pii &a, pii &b)
	{
		return map[a.first][a.second] < map[b.first][b.second];
	}
};

/*
높이 내림차순 기준 우선순위 큐
*/
priority_queue<pii, vector<pii>, comp> pq;
int cnt = 0;
void traverse(int i, int j)
{
	pq.push(make_pair(i, j));
	while (!pq.empty())
	{
		// cnt++;
		int x = 0, y = 0;
		x = pq.top().first;
		y = pq.top().second;
		pq.pop();

		if (visited[x][y] == true)
			continue;
		visited[x][y] = true;

		// 	현재 높이를 저장
		ull cur_height = 0;
		cur_height = map[x][y];

		int dx[4] = {-1, 1, 0, 0};
		int dy[4] = {0, 0, 1, -1};

		for (int d = 0; d < 4; d++)
		{
			int next_x = 0, next_y = 0;
			next_x = x + dx[d];
			next_y = y + dy[d];

			// 인덱스를 벗어난 곳은 그냥 통과한다
			if (next_x < 0 || next_x >= M || next_y < 0 || next_y >= N)
				continue;

			// 내리막길이라면
			if (cur_height > map[next_x][next_y])
			{
				// 경로의 개수를 전파한다
				dp[next_x][next_y] += dp[x][y];
				// 우선순위 큐에 저장한다
				pq.push(make_pair(next_x, next_y));
			}
		}
		// cout << "cnt = " << cnt << "\n";
		// for (int i = 0; i < M; i++)
		// {
		// 	for (int j = 0; j < N; j++)
		// 		cout << dp[i][j] << " ";
		// 	cout << "\n";
		// }
		// cout << "\n=====================\n";
	}
}

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

	// init map
	for (int i = 0; i < M; i++)
	{
		vector<ull> row;
		vector<bool> row_visited;
		for (int j = 0; j < N; j++)
		{
			ull input;
			cin >> input;
			row.push_back(input);
			row_visited.push_back(false);
		}
		map.push_back(row);
		visited.push_back(row_visited);
	}

	dp[0][0] = 1;
	traverse(0, 0);

	cout << dp[M - 1][N - 1];
}