problem solving/백준

[C++] 백준 2096번 : 내려가기

u1qns 2024. 6. 10. 23:55

이 문제를 풀다가 메모리 제한 때문에 틀렸습니다를 경험했기 때문에, 저처럼 고뇌하시는 분들을 위해 이 글을 씁니다. 🙂

 


🔗 문제

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

 

 

✏️ 풀이

 

이 문제가 원하는 답은 2개다. 게임 결과의 최대, 최소이다.

  1. 어떻게 하면 최대를 구할 수 있을까?
  2. 어떻게 하면 최소를 구할 수 있을까?

결론적으로, 최대값과 최소값을 구하는 점화식은 거의 동일합니다. 각 선택지마다 갈 수 있는 경로가 정해져 있기 때문에, 바로 직전의 위치만 비교하면 됩니다. 이렇게 최대값(max_dp)과 최소값(min_dp)을 갱신해 나가면 됩니다. (코드는 아래를 참고하세요.)

 

for(int i=0; i<N; ++i)
{
    max_dp[i+1][0] = std::max(max_dp[i][0], max_dp[i][1]) + arr[i+1][0];
    min_dp[i+1][0] = std::min(min_dp[i][0], min_dp[i][1]) +  arr[i+1][0];

    max_dp[i+1][1] = std::max(std::max(max_dp[i][0], max_dp][1]), max_dp[i][2]) + arr[i+1][1];
    min_dp[i+1][1] = std::min(std::min(min_dp[i][0], min_dp[i][1]), min_dp[i][2]) + arr[i+1][1];

    max_dp[i+1][2] = std::max(max_dp[i][1], max_dp[i][2]) + arr[i+1][2];
    min_dp[i+1][2] = std::min(min_dp[i][1], min_dp[i][2]) + arr[i+1][2];
}

 

메모리 제한

이 문제는 단순히 점화식을 짜는 것이지만, 골드 문제인 이유는 메모리 조건에 있습니다. 메모리 제한이 4MB입니다. 4MB면 대략적으로 int는 100만 개 이하로 선언할 수 있습니다. 하지만 메인 함수가 얼마나 메모리를 사용할지 모르니, 안전하게 50만 개로 잡는게 좋겠다.. 생각만 하고 시작했습니다. 

 

문제에서 N은 최대 10만 개입니다. 기존 입력은 3 * 10만 개로 30만 개, DP를 작성하기 위한 최대, 최소 배열은 각각 30만 개씩 필요합니다. 총 90만 개의 배열을 선언하게 되면????????????????????????  메모리 초과가 발생합니다. 

 

  • 기존 입력 3*10만개 → 30만개
  • DP를 작성하기 위한 최대, 최소 배열 → 30만개, 30만개
  • 총 : 90만개

 

해결 방법

점화식을 다시 살펴보면, 순서대로 나아가면서 계산이 완료되면 이전 값은 더 이상 사용하지 않습니다. 입력값은 현재 값만 사용하므로 3개, DP 또한 바로 직전의 값과 현재의 값만 필요하므로 각각 2 * 3개씩 필요합니다. 총 15개의 메모리만 필요합니다.

정말 조금만 더 생각해보면, 총 90만 개에서 15개로 메모리를 줄일 수 있습니다.

또한, 최대값을 구한 후 배열을 재활용하여 최소값을 구하면 메모리를 더 절약할 수 있습니다. 단, 이 경우 시간은 조금 더 소요될 수 있습니다.

 

  • 입력값은 현재값만 사용한다 → 3개
  • dp 또한 바로 직전의 값과 현재의 값만 필요하다. → 2x3개, 2x3개
  • 총 : 15개

 

정말 아주 조금만 더 생각해보는걸로 바로 총 90만개에서 15개로 메모리를 줄일 수 있었다. 

이런 메모리제이션 문제에서 대부분 넉넉히 배열을 썼는데 다시 생각하게 해준 문제였다. 👀

 

    bool idx = true;
    for(int i=1; i<=N; ++i)
    {
        std::cin >> arr[0] >> arr[1] >> arr[2]; // 현재값만 사용

        // idx 현재
        // !idx 직전
        max_dp[idx][0] = std::max(max_dp[!idx][0], max_dp[!idx][1]) + arr[0];
        min_dp[idx][0] = std::min(min_dp[!idx][0], min_dp[!idx][1]) +  arr[0];

        max_dp[idx][1] = std::max(std::max(max_dp[!idx][0], max_dp[!idx][1]), max_dp[!idx][2]) + arr[1];
        min_dp[idx][1] = std::min(std::min(min_dp[!idx][0], min_dp[!idx][1]), min_dp[!idx][2]) + arr[1];

        max_dp[idx][2] = std::max(max_dp[!idx][1], max_dp[!idx][2]) + arr[2];
        min_dp[idx][2] = std::min(min_dp[!idx][1], min_dp[!idx][2]) + arr[2];

        idx = !idx; // toggle
    }

+ 최대, 최대 배열도 하나의 배열만 선언한 후, 재사용하면 메모리를 더 아낄 수 있습니다. 시간을 2배로 쓰겠지만 일단 제출 시 시간초과는 아니네요

 

 

 

💾  소스

#include <iostream>

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

    int N;
    std::cin >> N;

    int arr[3] = {0, };
    int max_dp[2][3] = {0, };
    int min_dp[2][3] = {0, };
    
    bool idx = true;
    for(int i=1; i<=N; ++i)
    {
        std::cin >> arr[0] >> arr[1] >> arr[2];

        max_dp[idx][0] = std::max(max_dp[!idx][0], max_dp[!idx][1]) + arr[0];
        min_dp[idx][0] = std::min(min_dp[!idx][0], min_dp[!idx][1]) +  arr[0];

        max_dp[idx][1] = std::max(std::max(max_dp[!idx][0], max_dp[!idx][1]), max_dp[!idx][2]) + arr[1];
        min_dp[idx][1] = std::min(std::min(min_dp[!idx][0], min_dp[!idx][1]), min_dp[!idx][2]) + arr[1];

        max_dp[idx][2] = std::max(max_dp[!idx][1], max_dp[!idx][2]) + arr[2];
        min_dp[idx][2] = std::min(min_dp[!idx][1], min_dp[!idx][2]) + arr[2];

        idx = !idx;
    }

    int max_score = 0, min_score = 1e9;
    for(int i=0; i<3; ++i)
    {
        max_score = std::max(max_score, max_dp[!idx][i]);
        min_score = std::min(min_score, min_dp[!idx][i]);
    }

    std::cout << max_score << " " << min_score;

    return 0;
}