더 이상 tistory 블로그를 운영하지 않습니다. glanceyes.github.io에서 새롭게 시작합니다.

새소식

알고리즘/BOJ 풀이

BOJ 백준 19645번 햄최몇?

  • -

 

BOJ 백준 19645 햄최몇?

 

문제: https://www.acmicpc.net/problem/19645

 

19645번: 햄최몇?

세 모질이들 관우, 철환, 길원이가 모였다. 모질이들은 모이면 서로 '햄버거 최대 몇 개 드실 수 있나요?'의 준말인 '햄최몇?'을 시전하며 자랑을 하기 바쁘다. 막내 길원이는 문득 중요한 사실을

www.acmicpc.net

 

N개의 햄버거가 있고, 각 햄버거마다 먹을 때 얻을 수 있는 효용 값이 정해져 있다. 또한 섭취한 햄버거의 효용 값은 누적된다. 두 명의 선배와 막내 길원이가 모여서 N개의 햄버거를 먹을 때, 선배가 더 많은 효용을 얻게 하면서 막내가 얻을 수 있는 효용의 최댓값을 구하는 것이 문제이다.

 

처음에는 Dynamic Programming 풀이법으로 접근하여 다음과 같이 메모이제이션 할 배열을 정의했다.

 

 

dp[i][x][y]: i번째 햄버거까지 봤고, 첫 번째 선배가 얻은 효용이 x, 두 번째 선배가 얻은 효용이 y일 때, 막내가 얻을 수 있는 효용의 최댓값

 

그런데 생각해보면 사실 x 값과 y 값이 결정되면 막내가 얻는 효용 값은 자동적으로 정해진다. 전체 햄버거의 효용 값의 합에서 x와 y 값을 빼면 그게 바로 막내가 얻는 효용 값이다. 따라서 다른 방식의 접근이 필요해보였다.

 

 

dp[i][x][y]: i번째 햄버거까지 봤을 때, 첫 번째 선배가 얻은 효용이 x, 두 번째 선배가 얻은 효용이 y인 경우가 가능한지의 여부

 

하지만 위의 배열을 정의하면 메모리 제한 초과가 발생한다. 각 선배가 얻을 수 있는 효용의 최댓값이 2500이므로 50 * 2500^2는 문제에서 주어진 메모리 제한을 넘는다. (사실 1024MB여서 메모리 초과가 나오진 않는다.)

 

좀 더 나아가서 과연 i번째 햄버거까지 봤다는 정보가 필요한지 의문이 들었다. 사실 어떠한 햄버거를 이제까지 먹어서 첫 번째와 두 번째 선배가 누적한 효용값이 x, y가 되는 게 가능한지가 중요한 게 아니라, 그러한 경우가 가능한지 그 자체가 관건이다. 따라서 i번째 햄버거에 관한 정보는 기억할 필요가 없고, 다음과 같이 배열의 정의를 수정했다.

 

 

dp[x][y]: 첫 번째 선배가 얻은 효용이 x, 두 번째 선배가 얻은 효용이 y인 경우가 가능한지의 여부

 

첫 번째부터 N번째 햄버거까지 차례대로 탐색한다. i - 1번째 햄버거까지 살핀 이전 상태에서 선배 둘 중 한 사람이 i번째 햄버거를 먹었을 때, 선배들의 효용 누적 값이 각각 x, y가 될 수 있는지를 구한다. 이를 간단히 정리하면 다음과 같다.

 

 

 

a[i]: i번째 햄버거를 먹을 때 얻게 되는 효용
sum: 모든 햄버거 효용 값의 합
dp[x][y] |= (dp[x - a[i]][y] || dp[x][y - a[i]])

 

 

 

주의할 점은 이를 구하는 과정에서 선배의 효용값이 커야한다는 조건을 판단하지 않아야 한다. 미리 모든 경우의 수에 관해 가능한지를 구해놓고, 최종적으로 나중에 선배의 효용값이 큰 경우 중에서 막내가 얻는 효용의 최댓값을 따로 알아보면 된다. 이유를 단순하게 설명하자면, i번째 햄버거까지 탐색한 상태에서는 막내의 효용값이 선배들보다 크다 하더라도 i + 1번째 햄버거부터 선배들이 나머지 대부분을 먹게 했을 때 결과적으로 선배의 효용값이 막내보다 크게 될 가능성이 있어서다.

 

문제를 풀고 나서 검색해보니 이러한 풀이를 Knapsack 문제 풀이법의 일종으로 보는 것 같다. 사실 Knapsack 문제가 Dynamic Programming 알고리즘으로 해결 가능한 문제이니까 어찌 보면 당연한 말인 듯 싶다.

 

 

#include <cstdio>
#include <algorithm>
using namespace std;


const int N_MAX = 50;
const int T_MAX = 2500;


int n;
int a[N_MAX + 1];
bool dp[T_MAX + 1][T_MAX + 1];


int main(){
    int sum = 0;
    scanf("%d", &n);
    
    for (int i = 1; i <= n; i++){
        scanf("%d", &a[i]);
        sum += a[i];
    }
    
    dp[0][0] = true;
    
    for (int i = 1; i <= n; i++){
        for (int j = sum; j >= 0; j--){
            for (int k = sum; k >= 0; k--){
                if (j - a[i] >= 0) {
                    dp[j][k] |= dp[j - a[i]][k];
                }
                if (k - a[i] >= 0){
                    dp[j][k] |= dp[j][k - a[i]];
                }
            }
        }
    }
    
    int ans = 0;
    for (int i = 0; i <= sum; i++){
        for (int j = 0; j <= i; j++){
            if (dp[i][j] && j >= (sum - i - j)){
                ans = max(ans, sum - i - j);
            }
        }
    }
    
    printf("%d\n", ans);
    
    return 0;
}

 

Contents

글 주소를 복사했습니다

부족한 글 끝까지 읽어주셔서 감사합니다.
보충할 내용이 있으면 언제든지 댓글 남겨주세요.