👀 문제 설명
NASA에서는 화성 탐사를 위해 화성에 무선 조종 로봇을 보냈다. 실제 화성의 모습은 굉장히 복잡하지만, 로봇의 메모리가 얼마 안 되기 때문에 지형을 N×M 배열로 단순화 하여 생각하기로 한다.
지형의 고저차의 특성상, 로봇은 움직일 때 배열에서 왼쪽, 오른쪽, 아래쪽으로 이동할 수 있지만, 위쪽으로는 이동할 수 없다. 또한 한 번 탐사한 지역(배열에서 하나의 칸)은 탐사하지 않기로 한다.
각각의 지역은 탐사 가치가 있는데, 로봇을 배열의 왼쪽 위 (1, 1)에서 출발시켜 오른쪽 아래 (N, M)으로 보내려고 한다. 이때, 위의 조건을 만족하면서, 탐사한 지역들의 가치의 합이 최대가 되도록 하는 프로그램을 작성하시오.
입력
첫째 줄에 N, M(1≤N, M≤1,000)이 주어진다. 다음 N개의 줄에는 M개의 수로 배열이 주어진다. 배열의 각 수는 절댓값이 100을 넘지 않는 정수이다. 이 값은 그 지역의 가치를 나타낸다.
출력
첫째 줄에 최대 가치의 합을 출력한다.
예제 입력 1
5 5
10 25 7 8 13
68 24 -78 63 32
12 -69 100 -29 -25
-16 -22 -57 -33 99
7 -76 -11 77 15
예제 출력 1
319
✍🏻풀이
앞에 푼 '[Baekjoon] 욕심쟁이 판다(BOJ 1937)'랑 똑같이 Dynamic Programming으로 푸는 문제.
이 문제는 무조건 (1, 1)에서 시작해서 (n, m)에서 끝난다는 점이 다른 것 같다고 생각했다.
비슷하게 풀면 된다고 생각해서 풀었는데 자꾸 오류가 난다 :(
[처음 생각한 방법]
1. n과 m을 입력받고, 크기만큼 mars[MAX][MAX]에 각 가치를 입력 받는다.
이 때, dp[MAX][MAX]는 모두 0으로 초기화한다. (1, 1) 위치만 mars[1][1]과 같은 값으로 넣어준다.
dp[MAX][MAX]는 dynamic programming을 통해 각 자리에서 가치의 최대 합을 저장해두는 용도..
2. dx[3]과 dy[3]을 이용하여 왼쪽, 오른쪽, 아래쪽으로 움직임 - for문 사용
3. max 함수를 사용해서 이동한 자리에서 dp[i][j]의 기존값과 새로운 값을 비교하여 더 큰 값을 dp[i][j]에 저장해둔다.
4. 이동한 자리에서 dynamic programming 실행
이런 식으로 풀었는데 자꾸 오류가 난다! ㅠ
아래는 틀린 코드 :(
틀린 코드
//
// BOJ_2169.cpp
// Algorithm
//
// Created by 조수민 on 2020/05/18.
// Copyright © 2020 조수민. All rights reserved.
//
// 로봇 조종하기(https://www.acmicpc.net/problem/2169)
#include <stdio.h>
#include <iostream>
#define MAX 1000 // 1 <= N, M <= 1000
using namespace std;
int n, m;
int mars[MAX][MAX];
int dp[MAX][MAX]; // (1, 1)에서부터 (i, j)까지 이동했을 때 합이 최대가 되는 값들을 저장..?
int dx[3] = { 0, 0, 1 }; // (왼쪽, 오른쪽, 아래쪽)
int dy[3] = { -1, 1, 0 }; // (왼쪽, 오른쪽, 아래쪽)
void input()
{
cin >> n >> m;
for (int i = 0; i <=n; i++)
for (int j = 0; j <= m; j++)
{
dp[i][j] = 0;
if (i == 0 || j == 0)
mars[i][j] = 0;
else if (i == 1 && j == 1)
{
cin >> mars[i][j];
dp[i][j] = mars[i][j]; // dp[1][1]에만 mars[1][1]과 같은 값을 넣어둔다.
}
else
cin >> mars[i][j];
}
}
int dynamic_programming(int x, int y)
{
int now_answer = dp[x][y];
// 오른족 아래(n, m)에 도착하면 (n, m)위치의 값을 더해 그 값을 return 한다.
if (x == n && y == m)
return now_answer + mars[x][y];
for (int i = 0; i < 3; i++)
{
int next_x = x + dx[i];
int next_y = y + dy[i];
// 범위를 벗어나면 아래 구문을 수행하지 않고 다음번으로 넘어간다.
if (next_x <= 0 || next_x > n || next_y <= 0 || next_y > m)
continue;
// 오른족 아래(n, m)에 도착하면 (n, m)위치의 값을 더해 그 값을 return 한다.
if (next_x == n && next_y == m)
return now_answer + mars[next_x][next_y];
// 제대로 된 위치라면 ..
// 이동해서 또 수행 ..?
// int next_dynamic = dynamic_programming(next_x, next_y);
// now_answer = max(now_answer, next_dynamic);
// dp[next_x][next_y] = now_answer;
dp[next_x][next_y] = max(now_answer, now_answer + mars[next_x][next_y]);
dynamic_programming(next_x, next_y);
}
return now_answer;
}
int max_answer()
{
int answer = dynamic_programming(1, 1);
return answer;
}
int main()
{
input();
cout << max_answer() << endl;
return 0;
}
구글링을 통해 알아낸 방법은, 이 문제는 dx, dy와 for문을 사용해 풀면 더 복잡하다는 것..
아래와 같이 해결했다!
상수 선언
#define MAX 1001 : 1 <= N, M <= 1000 이기 때문에 1001로 선언했다.
필요한 변수
int n, m : 지형을 나타내는 n과 m
int mars[MAX][MAX] : 각 지역들의 가치를 담은 배열
int dp[MAX][MAX] : (1, 1)부터 (i, j)까지 이동했을 때의 합이 최대가 되는 값들을 저장
int leftRightCompare[2][MAX] : i가 2부터 n일 때, dp[i][j]를 구하기 위해서는 비교를 해야 한다. 비교를 하기 위한 배열
알고리즘
최종 답은 dp[n][m]이 되기 때문에, dp[i][j]만 구하면 되는데.. 이게 너무 복잡하다 ㅠ
mars[i][j]와 dp[i][j]의 초기 형태는 이렇게 된다.
dp[i][j]를 채워넣으면 된다!
로봇은 왼쪽, 오른쪽, 아래쪽으로만 이동할 수 있다!
dp[i][j]에서 i가 1일 때를 보면,
이미 지나간 길은 되돌아갈 수 없기 때문에, j가 2~5(m)까지의 dp[i][j]값은 mars[i][j] + dp[i][j - 1]이 된다.
이것을 코드로 옮겨보면
// i = 1일 때
// 이미 지나온 길은 다시 가지 못하므로, 오른쪽으로 갈 수밖에 없다.
for (int j = 2; j <= m; j++)
dp[1][j] = mars[1][j] + dp[1][j - 1];
이렇게 된다!
이제 문제는 i가 2부터 5(n)까지일 때인데, case를 둘로 나눌 수 있다.
case 1) 왼쪽 -> 오른쪽으로 이동하는 경우
case 2) 오른쪽 -> 왼쪽으로 이동하는 경우
그런데, 각각의 case에서 아래로 이동하는 경우와 또 비교를 해줘야 한다.
이 때 아래로 이동한다는 것은 예로 들자면,
'25'에서 아래로 이동하는 것은 '24'의 입장에서 위에서 온다는 뜻과 같다.
이걸 유의하고,
각각의 case에서 아래로 이동하는 경우와 비교를 해야 한다는 것은
case 1) 왼쪽 -> 오른쪽 이동
'24'까지 가는 방법에는 '10' -> '68' -> '24', 즉 왼쪽 -> 오른쪽으로 이동하는 방법과
'35' -> '24', 즉 위 -> 아래로 이동하는 방법이 있으므로, 이 두 방법 중 더 큰 값을 leftRightCompare[0][j]에 넣는다.
case 2) 오른쪽 -> 왼쪽 이동
위와 마찬가지로 '24'까지 가는 방법에는
'63' -> '32' -> '63' -> '-78' -> '24' 오른쪽 -> 왼쪽으로 이동하는 방법과
'35' -> '24' 위 -> 아래로 이동하는 방법이 있다. 이 둘 중 더 큰 값을 leftRightCompare[1][j]에 넣는다.
위 작업이 끝났다면, 마지막으로 case 1과 case 2를 비교해 더 큰 값을 dp[i][j]에 넣어주면 된다!
다시 정리하면,
1. case 1) 왼쪽 -> 오른쪽 이동시 max(왼 -> 오, 위 -> 아래) 한 값을 leftRightCompare[0][j]에 대입
2. case 2) 오른쪽 -> 왼쪽 이동시 max(오 -> 왼, 위 -> 아래) 한 값을 leftRightCompare[1][j]에 대입
3. leftRightCompare[0][j]와 leftRightCompare[1][j]를 비교, 더 큰 값을 dp[i][j]에 대입
이 된다.
이걸 코드로 옮기면 아래와 같다!
// i = 2~n일 때
// case 1) leftRightCompare[0][j]는 왼쪽 -> 오른쪽으로 이동했을 때
// case 2) leftRightCompare[1][j]는 오른쪽 -> 왼쪽으로 이동할 때
// case 1과 case 2의 값을 비교해 더 큰 값을 dp[i][j]에 넣는다.
for (int i = 2; i <= n; i++)
{
// case 1) 왼쪽 -> 오른쪽 이동했을 때
leftRightCompare[0][0] = dp[i - 1][1]; // 제일 처음 값은 위에서 내려온 값
// 왼쪽 -> 오른쪽 오는 값 vs 위에서 내려오는 값 비교, 큰 값을 넣어준다.
for (int j = 1; j <= m; j++)
leftRightCompare[0][j] = max(leftRightCompare[0][j - 1], dp[i - 1][j]) + mars[i][j];
// case 2) 오른쪽 -> 왼쪽 이동했을 때
leftRightCompare[1][m + 1] = dp[i - 1][m]; // 제일 마지막 값은 위에서 내려온 값
// 오른쪽 -> 왼쪽 오는 값 vs 위에서 내려오는 값 비교, 큰 값을 넣어준다.
for (int j = m; j >= 1; j--)
leftRightCompare[1][j] = max(leftRightCompare[1][j + 1], dp[i - 1][j]) + mars[i][j];
// case 1과 case 2 값 비교
for (int j = 1; j <= m; j++)
dp[i][j] = max(leftRightCompare[0][j], leftRightCompare[1][j]);
이제 이 코드들을 합치면 끝 😭
코드
//
// BOJ_2169.cpp
// Algorithm
//
// Created by 조수민 on 2020/05/18.
// Copyright © 2020 조수민. All rights reserved.
//
// 로봇 조종하기(https://www.acmicpc.net/problem/2169)
#include <iostream>
#define MAX 1001 // 1 <= N, M <= 1000
using namespace std;
int n, m;
int mars[MAX][MAX] = { 0 };
int dp[MAX][MAX] = { 0 }; // (1, 1)에서부터 (i, j)까지 이동했을 때 합이 최대가 되는 값들을 저장
int leftRightCompare[2][MAX] = { 0 };
void input()
{
cin >> n >> m; // n,m 입력받기
// mars[i][j]를 입력받는다.
for (int i = 1; i <=n; i++)
for (int j = 1; j <= m; j++)
cin >> mars[i][j];
// (1, 1)에서 시작하므로 (1, 1)은 꼭 들어가야 하는 값
dp[1][1] = mars[1][1]; // dp[1][1]은 mars[1][1]과 같은 값으로 초기화해둔다.
}
int getMax()
{
// i = 1일 때
// 이미 지나온 길은 다시 가지 못하므로, 오른쪽으로 갈 수밖에 없다.
for (int j = 2; j <= m; j++)
dp[1][j] = mars[1][j] + dp[1][j - 1];
// i = 2~n일 때
// case 1) leftRightCompare[0][j]는 왼쪽 -> 오른쪽으로 이동했을 때
// case 2) leftRightCompare[1][j]는 오른쪽 -> 왼쪽으로 이동할 때
// case 1과 case 2의 값을 비교해 더 큰 값을 dp[i][j]에 넣는다.
for (int i = 2; i <= n; i++)
{
// case 1) 왼쪽 -> 오른쪽 이동했을 때
leftRightCompare[0][0] = dp[i - 1][1]; // 제일 처음 값은 위에서 내려온 값
// 왼쪽 -> 오른쪽 오는 값 vs 위에서 내려오는 값 비교, 큰 값을 넣어준다.
for (int j = 1; j <= m; j++)
leftRightCompare[0][j] = max(leftRightCompare[0][j - 1], dp[i - 1][j]) + mars[i][j];
// case 2) 오른쪽 -> 왼쪽 이동했을 때
leftRightCompare[1][m + 1] = dp[i - 1][m]; // 제일 마지막 값은 위에서 내려온 값
// 오른쪽 -> 왼쪽 오는 값 vs 위에서 내려오는 값 비교, 큰 값을 넣어준다.
for (int j = m; j >= 1; j--)
leftRightCompare[1][j] = max(leftRightCompare[1][j + 1], dp[i - 1][j]) + mars[i][j];
// case 1과 case 2 값 비교
for (int j = 1; j <= m; j++)
dp[i][j] = max(leftRightCompare[0][j], leftRightCompare[1][j]);
}
return dp[n][m];
}
int main()
{
input();
cout << getMax() << endl;
return 0;
}
이거 하루종일 잡고 있었다..😭 DP 너무 어렵다.. 금방 풀 줄 알았는데 .. :(
[참고] 블로그
'숨막히는 알고말고 > 문제 풀이' 카테고리의 다른 글
[Baekjoon] 피보나치 수의 확장(BOJ 1788) (0) | 2020.07.15 |
---|---|
[Baekjoon] 정상회담 2(BOJ 1670) (0) | 2020.07.15 |
[Baekjoon] 토너먼트(BOJ 1057) (0) | 2020.06.23 |
[Baekjoon] 약수(BOJ 1037) (2) | 2020.06.20 |
[Baekjoon] 욕심쟁이 판다(BOJ 1937) (0) | 2020.05.18 |