본문 바로가기
ps

[백준] 3114 - 사과와 바나나

by kariskan 2023. 8. 7.

https://www.acmicpc.net/problem/3114

 

3114번: 사과와 바나나

첫 번째 예제의 경우 불도저가 오른쪽-아래, 오른쪽-아래, 아래로 이동하면 된다. 경로의 아래에 있는 사과 나무의 개수는 3+2+4=9개이고, 위에 있는 바나나 나무의 개수는 3+5=8개이다.

www.acmicpc.net

 

문제를 보고 prefix sum + dp이라는 것을 알아차리고 간단하게 해결하려 했지만 WA를 받았다. 먼저 처음 시도한 방법부터 알아보자.

 

map[i][j] = 입력

asum[i][j] = j열의 i행 까지의 사과나무의 누적합(map[1][j] + map[2][j] + ... map[i][j])

bsum[i][j] = j열의 i행 까지의 바나나나무의 누적합(map[1][j] + map[2][j] + ...map[i][j])

dp[i][j] = (i,j)에 도달했을 때 얻을 수 있는 나무의 합의 최댓값

라고 했을 때, dp[i][j]는 3가지의 경우로 채워질 수 있다.

 

  • (i - 1, j - 1)에서 (i, j)로 도달하는 경우
    • (i, j) 위의 바나나 나무와 (i, j) 아래의 사과나무를 누적합으로 구하면 된다.
  • (i, j - 1)에서 (i, j)로 도달하는 경우
    • (i, j) 위의 바나나 나무와 (i, j) 아래의 사과 나무를 누적합으로 구하면 된다.
  • (i - 1, j)에서 (i, j)로 도달하는 경우
    • (i, j)가 사과나무일 경우, dp[i - 1][j]에서 map[i][j]만큼 빼면 된다.

 

이렇게만 구현한 결과가 아래와 같다.

#include <iostream>
using namespace std;

int n, m, map[1501][1501], dp[1501][1501], asum[1501][1501], bsum[1501][1501];

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

    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            char a;
            int b;
            cin >> a >> b;
            asum[i][j] = asum[i - 1][j];
            bsum[i][j] = bsum[i - 1][j];
            if (a == 'A') {
                map[i][j] = b;
                asum[i][j] += b;
            } else {
                map[i][j] = -b;
                bsum[i][j] += b;
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            dp[i][j] = dp[i - 1][j - 1] + (asum[n][j] - asum[i][j]) + (bsum[i - 1][j]);
            dp[i][j] = max(dp[i][j], dp[i][j - 1] + (asum[n][j] - asum[i][j]) + (bsum[i - 1][j]));
            if (map[i][j] > 0) {
                dp[i][j] = max(dp[i][j], dp[i - 1][j] - map[i][j]);
            } else {
                dp[i][j] = max(dp[i][j], dp[i - 1][j]);
            }
        }
    }
    cout << dp[n][m];
}

 

하지만 틀린 답이었다. 어디가 틀렸을까? 위 코드로 문제 예제 1번을 돌리면 dp 배열은 다음과 같다.

5 9 13
4 12 20
2 8 20
2 8 17

 

이것이 과연 맞는 것일까?

입력이 다음과 같다고 생각해 보자.

4 1
B2
A3
A2
B1

답은 분명히 0인데, dp 배열은 위에서 아래로 5 4 2 2를 나타낼 것이다.

 

결론은, 맨 처음 열에 대해서는 갈 수 있는 과정이 단 하나(아래로만 내려가는 것)이기 때문에 바나나 나무는 얻을 수 없다. 따라서 첫째 열만 미리 전처리 해주고, 두 번째 열부터 dp 점화식을 적용하면 된다.

 

#include <iostream>
using namespace std;

int n, m, map[1501][1501]; // map[i][j] > 0이면 사과, map[i][j] < 0이면 바나나
int dp[1501][1501];
int asum[1501][1501], bsum[1501][1501];

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

    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            char a;
            int b;
            cin >> a >> b;
            asum[i][j] = asum[i - 1][j];
            bsum[i][j] = bsum[i - 1][j];
            if (a == 'A') {
                map[i][j] = b;
                asum[i][j] += b;
            } else {
                map[i][j] = -b;
                bsum[i][j] += b;
            }
        }
    }
    
    // 전처리
    for (int i = 1; i <= n; i++) {
        dp[i][1] = asum[n][1] - asum[i][1];
    }

    for (int i = 1; i <= n; i++) {
        for (int j = 2; j <= m; j++) {
            // 왼쪽 위에서 오는 경우
            dp[i][j] = dp[i - 1][j - 1] + (asum[n][j] - asum[i][j]) + (bsum[i - 1][j]);

            // 왼쪽에서 오는 경우
            dp[i][j] = max(dp[i][j], dp[i][j - 1] + (asum[n][j] - asum[i][j]) + (bsum[i - 1][j]));

            // 위에서 오는 경우, 사과나무의 누적합만 변하게 된다
            if (map[i][j] > 0) {
                // 현재 도달한 칸이 사과나무 일때는 위의 dp값에서 빼줘야 한다
                dp[i][j] = max(dp[i][j], dp[i - 1][j] - map[i][j]);
            } else {
                dp[i][j] = max(dp[i][j], dp[i - 1][j]);
            }
        }
    }
    cout << dp[n][m];
}

'ps' 카테고리의 다른 글

[백준] 14864 - 줄서기  (0) 2023.08.14
[백준] 2917 - 늑대 사냥꾼  (0) 2023.08.08
[백준] 3117 - YouTube  (0) 2023.08.02
[백준] 21922 - 학부 연구생 민상  (0) 2023.08.02
[백준] 2281 - 데스노트  (0) 2023.08.01

댓글