Please sign in first.
Official
D - お土産選び / Choosing Souvenirs Editorial
by
D - お土産選び / Choosing Souvenirs Editorial
by
MtSaka
この問題は個数制限付きナップサック問題です。
素直な01ナップサックの要領の動的計画法で時間計算量 \(\mathrm{O}(S(\sum c_i ))\) などになります。
種類数である \(N\) が小さいのでこれを活かす方法を考えます。実は各種類について最大で \(c_i\) 個選べるというのは \(O(\log c_i)\) 個の商品に言い換えられます。具体的には、以下の操作を繰り替えして得られた商品を考えます。
int k = 1;
while (c[i] > 0) {
add(w[i] * k, v[i] * k); // k個をまとめて1つの商品にする
c[i] -= k;
k = min(2 * k, c[i]);
}
これでよい理由は \(1,2,4,\ldots,2^k,c_i-2^{k+1}+1\) 個がそれぞれまとめられて \(1\) つの商品とされた \(k+2\) 個の商品のうち\(0\) 個以上いくつか選ぶことで必ず \(0\) から \(c_i\) 個まで選ぶ個数を網羅できるからです。(2進数で\(0\) から 2^{k+1}-1をカバーし、残りの部分を最後の一つの商品で補っている)
これで、商品の個数を \(\mathrm{O}(N\log \max c)\) 個に言い換えることができた。これに01ナップサックの動的計画法を適用すると全体で時間計算量 \(\mathrm{O}(SN\log \max c)\) で解くことができます。
bonus: \(\mathrm{O}(SN)\) でこの問題を解いてください。
実装例(C++)
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, s;
cin >> n >> s;
vector<int> dp(s + 1, -1e9);
dp[0] = 0;
auto add = [&](int w, int v) -> void {
for (int i = s; i >= w; --i) {
if (dp[i - w] >= 0) dp[i] = max(dp[i], dp[i - w] + v);
}
};
for (int i = 0; i < n; ++i) {
int w, v, c;
cin >> w >> v >> c;
int k = 1;
while (c > 0) {
add(k * w, k * v);
c -= k;
k = min(2 * k, c);
}
}
if (dp[s] == -(int)1e9)
cout << -1 << endl;
else
cout << dp[s] << endl;
}
posted:
last update:
