Official

N - 活動 / Activities Editorial by tatyam


この問題は、一見 ナップサック問題 のように見えますが、活動を行う順番によって得点が変わり得るので、このままではナップサック問題と同様に処理することは難しいです。
しかし、\(a_i\) が大きく \(b_i\) が小さい活動から優先的に行ったほうが、得点が高くなりやすそうです。
そこで、行う活動を決めたときに、どのような順番で行えば得点が最大になるかについて考えてみましょう。

活動 \(1\) と活動 \(2\) を行う場合を考えます。
活動 \(1\) を先に行う場合、得点は \(a_1H+a_2(H-b_1)\) 、活動 \(2\) を先に行う場合、得点は \(a_2H+a_1(H-b_2)\) です。

\(a_1H+a_2(H-b_1) ≥ a_2H+a_1(H-b_2)\) を式変形すると、

\[ \begin{aligned} a_1H+a_2(H-b_1) &≥ a_2H+a_1(H-b_2)\\ a_1b_2 &≥ a_2b_1\\ \frac{a_1}{b_1} &≥ \frac{a_2}{b_2}\\ \end{aligned} \]

となることから、\(\dfrac{a_i}{b_i}\) が大きい活動から順に行えば良いことが分かります。

\(3\) つ以上の活動を行う場合でも、\(\dfrac{a_i}{b_i}\) の昇順になっている箇所が \(1\) 箇所でもあれば、そこを交換することで得点が上がるので、得点を最大化するには、\(\dfrac{a_i}{b_i}\) の降順に行わなければならないことが分かります。

したがって、活動を \(\dfrac{a_i}{b_i}\) の降順でソートすれば、
\(dp[i]={}\)( 体力が \(i\) のときの得点の最大値 )
の DP テーブルを管理する、ナップサック問題と同様の DP で解くことができます。

回答例 (C++)

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
using ll = long long;
void chmax(ll& a, ll b){ if(a < b) a = b; }

int main(){
    ll N, H;
    cin >> N >> H;
    vector<pair<ll, ll>> activity(N);
    for(auto& [a, b] : activity) cin >> a >> b;
    sort(activity.begin(), activity.end(), [](auto x, auto y){ return x.first * y.second > x.second * y.first; });
    vector<ll> dp(H + 1);
    for(auto [a, b] : activity){
        for(ll h = 1; h <= H; h++) chmax(dp[max(0LL, h - b)], dp[h] + a * h);
    }
    cout << *max_element(dp.begin(), dp.end()) << endl;
}

回答例 (Python)

N, H = map(int, input().split())
act = [tuple(map(int, input().split())) for i in range(N)]
act.sort(key=lambda x: x[1] / x[0])
dp = [0] * (H + 1)
for a, b in act:
    if b > H:
        b = H
    for i in range(1, b):
        dp[0] = max(dp[0], dp[i] + i * a)
    for i in range(b, H + 1):
        dp[i - b] = max(dp[i - b], dp[i] + i * a)
print(max(dp))

posted:
last update: