Algorithm

[C++][백준] 욕심쟁이 판다 (1937) - dp

Belluga 2021. 8. 24. 22:47

문제

n × n의 크기의 대나무 숲이 있다. 욕심쟁이 판다는 어떤 지역에서 대나무를 먹기 시작한다. 그리고 그 곳의 대나무를 다 먹어 치우면 상, 하, 좌, 우 중 한 곳으로 이동을 한다. 그리고 또 그곳에서 대나무를 먹는다. 그런데 단 조건이 있다. 이 판다는 매우 욕심이 많아서 대나무를 먹고 자리를 옮기면 그 옮긴 지역에 그 전 지역보다 대나무가 많이 있어야 한다.

이 판다의 사육사는 이런 판다를 대나무 숲에 풀어 놓아야 하는데, 어떤 지점에 처음에 풀어 놓아야 하고, 어떤 곳으로 이동을 시켜야 판다가 최대한 많은 칸을 방문할 수 있는지 고민에 빠져 있다. 우리의 임무는 이 사육사를 도와주는 것이다. n × n 크기의 대나무 숲이 주어져 있을 때, 이 판다가 최대한 많은 칸을 이동하려면 어떤 경로를 통하여 움직여야 하는지 구하여라.

 

입력

첫째 줄에 대나무 숲의 크기 n(1 ≤ n ≤ 500)이 주어진다. 그리고 둘째 줄부터 n+1번째 줄까지 대나무 숲의 정보가 주어진다. 대나무 숲의 정보는 공백을 사이로 두고 각 지역의 대나무의 양이 정수 값으로 주어진다. 대나무의 양은 1,000,000보다 작거나 같은 자연수이다.

 

출력

첫째 줄에는 판다가 이동할 수 있는 칸의 수의 최댓값을 출력한다.

 

풀이

판다가 이동할 수 있는 칸의 수의 최댓값을 구하는 문제입니다. 

 

문제의 조건을 정리해보면 아래와 같습니다.

1. 판다는 현재 위치에서 대나무를 다 먹는다.

2. 상, 하, 좌, 우로 이동한다. 단, 현재 위치보다 대나무가 많은쪽으로 이동할 수 있다.

3. 최대한 많은 칸을 이동해야 한다.

 

먼저 2차원 배열 Map에 대나무 숲에 대한 정보를 입력받습니다. 

저는 DP로 문제를 풀기 위해 2차원 배열 Memo를 생성하였습니다.

Memo의 초기값은 0이며, 해당 칸을 밟았을 때 이동할 수 있는 칸의 수의 최댓값을 담고있습니다.

 

"현재 위치보다 대나무가 많은쪽으로 이동할 수 있다" 조건에 초점을 맞춰보겠습니다.

이 말은 즉 "대나무가 가장 많은쪽에서는 다른 위치로 이동할 수 없다"라고 생각할 수 있습니다.

즉 Memo(해당 칸을 밟았을 때 이동할 수 있는 칸의 수의 최댓값)를 1(현 위치는 밟았기 때문)로 채울 수 있습니다.

 

이렇게 대나무 수가 많은 위치부터 적은 위치로 Bottom-up(바텀업)느낌으로 Memo 배열을 채워주면 

어떤 칸을 밟았을 때 이동할 수 있는 칸의 수가 최대인지 구할 수 있습니다.

 

그림으로 살펴보면 이해가 쉬울 것 같습니다.

대나무가 가장 많은 위치(16)에서는 그 어느곳으로도 이동할 수 없기 때문에 

해당 칸을 밟았을 때 이동할 수 있는 칸의 수의 최댓값은 자기 자신을 밟은 1칸입니다.

이 값을 Memo 배열에 기록해둡니다.

 

 

그 다음 대나무가 15개 있는 위치에서도 그 어느곳으로도 이동할 수 없기 때문에 

해당 칸을 밟았을 때 이동할 수 있는 칸의 수의 최댓값은 자기 자신을 밟은 1칸입니다.

이 값을 Memo 배열에 기록해둡니다.

 

 

마찬가지로 14, 13, 12 칸 모두 자기 자신을 제외한 칸으로 이동할 수 없기 때문에 Memo 배열을 1로 채워주었습니다.

이 때 대나무가 11개 있는 위치에서는 대나무가 15개인 위치로 이동할 수 있습니다. 

따라서 대나무 15 위치의 Memo를 참고하여 (15개인 위치를 밟았을 때 이동할 수 있는 칸의 최댓값 + 1)을 Memo에 기록합니다.

 

 

이번엔 대나무가 4개 있는 위치를 살펴보도록 하겠습니다.

해당 위치에서는 모든 위치로 이동할 수 있습니다. 

 

이 때 ↑, ←, ↓ 중 어느 방향으로 이동해야 가장 많은 칸을 이동할 수 있을까요?

우리는 Memo 값만 비교해보고 결정할 수 있습니다. ↑(2), ←(3), ↓(1) 

Memo값이 가장 큰 왼쪽 ←(3) 정보를 통해 4 입장에서는 직접 가보지 않아도 5 위치만 밟으면 최대 3칸을 이동할 수 있다는 것을 파악할 수 있습니다. 

 

 

 

이렇게 모든 Memo 배열을 채울 수 있고

이동할 수 있는 칸의 최대 수는 Memo 배열의 최댓값 4가 됩니다.

 

전체 코드는 아래와 같습니다.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int N;
const int MAX = 520;
int map[MAX][MAX];
int memo[MAX][MAX] = { 0, };

int dirX[4] = {-1, 0, 1, 0};
int dirY[4] = {0, 1, 0, -1};

struct myData {
	int count;
	int x;
	int y;
};
vector<myData> list;

bool comp(const myData& a, const myData& b) {
	if (a.count == b.count) {
		if (a.x == b.x) {
			return a.y > b.y;
		}
		else {
			return a.x > b.x;
		}
	}
	else {
		return a.count > b.count;
	}
}

int main() {
	cin >> N;

	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			cin >> map[i][j];
			list.push_back({ map[i][j], i, j});
		}
	}
	sort(list.begin(), list.end(), comp);

	int answer = 0;
	for (int i = 0; i < list.size(); i++) {
		int curTree = list[i].count;
		int curX = list[i].x;
		int curY = list[i].y;

		int maxTree = 0;
		for (int j = 0; j < 4; j++) {
			int nextX = curX + dirX[j];
			int nextY = curY + dirY[j];

			if (map[nextX][nextY] <= map[curX][curY]) continue;
			if (memo[nextX][nextY] > maxTree) {
				maxTree = memo[nextX][nextY];
			}
		}
		memo[curX][curY] = maxTree + 1;
		if (memo[curX][curY] > answer) {
			answer = memo[curX][curY];
		}
	}
	cout << answer;

	return 0;
}

 

Memo 배열의 정의는 해당 칸을 밟았을 때 이동할 수 있는 칸의 수의 최댓값이기 때문에

밟을 수 없는 경우, 즉 현재 칸보다 값이 같거나 큰 경우에는 Memo값을 참조할 수 없습니다.

따라서 continue로 처리하였습니다.