블루브리ッジ 컵 국선 동적 프로그래밍 문제 해설

1. 정수 합 구성 문제 (2022)

서로 다른 10 개의 양정수를 선택하여 그 합이 2022 가 되는 경우의 수를 구하는 문제입니다. 이는 제한 조건이 두 개 존재하는 배낭 문제 변형으로 볼 수 있습니다.

접근 방식

1 부터 2022 까지의 숫자를物品으로 간주합니다. 각 숫자는 그 자체의 값을 비용 (weight) 으로 가지며, 선택된 숫자의 개수도 제한 조건이 됩니다. 따라서 상태 공간은 [선택된 개수][현재 합] 으로 정의할 수 있습니다. 기본적인 0/1 배낭 문제 로직에 개수를 세는 차원을 추가하여 구현합니다.

구현 코드

#include <iostream>
#include <vector>

using namespace std;

void solve() {
    // dp[count][sum]: count 개의 숫자를 선택하여 합이 sum 이 되는 경우의 수
    long long dp[11][2023] = {0};
    dp[0][0] = 1;

    // 1 부터 2022 까지의 숫자를 하나씩 고려
    for (int num = 1; num <= 2022; ++num) {
        // 개수는 역순으로 순회하여 중복 선택 방지 (0/1 배낭 최적화)
        for (int count = 10; count >= 1; --count) {
            // 합은 현재 숫자 이상부터 순회
            for (int currentSum = num; currentSum <= 2022; ++currentSum) {
                dp[count][currentSum] += dp[count - 1][currentSum - num];
            }
        }
    }

    cout << dp[10][2022] << endl;
}

2. 벽돌 쌓기 최적화 문제

주어진 n 개의 벽돌을 쌓을 때, 각 벽돌 위에 쌓인 벽돌들의 총 무게가 해당 벽돌의 내구도 (value) 를 초과하지 않도록 해야 합니다. 이때 얻을 수 있는 최대 내구도 합을 구하는 문제입니다.

접근 방식

벽돌을 쌓는 순서가 중요합니다. 위에 오는 벽돌일수록 무게가 가볍고 내구도가 큰 것이 유리합니다. 그리디 접근을 위해 각 벽돌의 무게 + 내구도 합을 기준으로 오름차순 정렬합니다. 정렬된 순서대로 배낭 문제 기법을 적용하여 최대 값을 계산합니다.

구현 코드

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

struct Brick {
    int weight;
    int durability;
};

void solve() {
    int n;
    cin >> n;
    vector<Brick> bricks(n);
    int totalWeight = 0;

    for (int i = 0; i < n; ++i) {
        cin >> bricks[i].weight >> bricks[i].durability;
        totalWeight += bricks[i].weight;
    }

    // 무게 + 내구도 합을 기준으로 정렬
    sort(bricks.begin(), bricks.end(), [](const Brick& a, const Brick& b) {
        return (a.weight + a.durability) < (b.weight + b.durability);
    });

    vector<int> dp(totalWeight + 1, 0);
    int maxValue = 0;

    for (const auto& brick : bricks) {
        for (int w = totalWeight; w >= brick.weight; --w) {
            // 현재 무게에서 해당 벽돌 무게를 뺀 나머지가 벽돌 내구도 이하여야 함
            if (w - brick.weight <= brick.durability) {
                dp[w] = max(dp[w], dp[w - brick.weight] + brick.durability);
            }
            maxValue = max(maxValue, dp[w]);
        }
    }

    cout << maxValue << endl;
}

3. 비용报销 날짜 제한 문제

여러 장의发票 (영수증) 가 있으며, 각각 날짜와 금액을 가지고 있습니다. 선택한发票들 간의 날짜 간격이 k 이상이어야 하며, 총 금액이 m 을 초과하지 않을 때 최대 금액을 구하는 문제입니다.

접근 방식

먼저 날짜를 연중 일 수 (1~365) 로 변환합니다. 각 날짜별로 받을 수 있는 최대 금액을 미리 계산해 둡니다. 이후 날짜를 순회하며, k 일 이전의 상태 값을 참조하여 현재 날짜의发票를 선택할지 말지 결정하는 동적 프로그래밍을 수행합니다.

구현 코드

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

// 월과 일을 연중 일 수로 변환
int convertDate(int month, int day) {
    const int daysInMonth[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int totalDays = day;
    for (int i = 1; i < month; ++i) {
        totalDays += daysInMonth[i];
    }
    return totalDays;
}

void solve() {
    int n, m, k;
    cin >> n >> m >> k;

    vector<int> maxExpense(366, 0);

    for (int i = 0; i < n; ++i) {
        int mm, dd, val;
        cin >> mm >> dd >> val;
        int dayIndex = convertDate(mm, dd);
        // 같은 날짜에 여러 장이 있다면 최대 값만 저장
        maxExpense[dayIndex] = max(maxExpense[dayIndex], val);
    }

    // DP 진행: dp[i] 는 i 일까지 고려했을 때의 최대 금액
    for (int i = 1; i <= 365; ++i) {
        int prevDay = max(0, i - k);
        // 현재 날짜의 금액을 더할 수 있는지 확인 (총합 제한 m 은 최종 결과에서 필터링하거나 상태에 포함 가능)
        // 여기서는 단순 최대합 구조로 작성하며 m 제한은 필요시 추가 조건으로 활용
        if (maxExpense[i] + maxExpense[prevDay] <= m) {
             maxExpense[i] = max(maxExpense[i - 1], maxExpense[i] + maxExpense[prevDay]);
        } else {
             maxExpense[i] = maxExpense[i - 1];
        }
    }

    cout << maxExpense[365] << endl;
}

태그: dynamic-programming knapsack-variant algorithm-optimization c-plus-plus

9월 2일 04:17에 게시됨