多重背包习题题解(洛谷题单)
本篇对应源码见仓库
solutions/xcpc/algorithm/dp/knapsack-bounded/。
题单
P1833 樱花
题意
是基础的弱化的多重背包问题,数据不强,用朴素解法也能过。
思路
模板题,注意处理 \(c = 0\) 的情况,可以根据数据范围确定上界。
示例代码
signed main() {
//IOS;
int hh, mm;
int T;
scanf("%d:%d", &hh, &mm);
T = hh * 60 + mm;
scanf("%d:%d", &hh, &mm);
T = hh * 60 + mm - T;
int n;
scanf("%d", &n);
vector<int> w(N), v(N);
int cnt = 0;
fer(i, 0, n) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if(!c) c = T / a + 1;
int k = 1;
while(k <= c) {
w[++cnt] = k * a;
v[cnt] = k * b;
c -= k;
k <<= 1;
}
if(c) {
w[++cnt] = c * a;
v[cnt] = c * b;
}
}
vector<int> dp(1010);
fer(i, 1, cnt + 1) {
ferd(j, T, w[i]) {
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
cout << dp[T] << '\n';
return 0;
}
P5365 SNOI2017 英雄联盟 P1077 摆花
题意
多重背包中恰好装满的方案数问题。
思路
不能直接用二进制优化解决,因为会重复计算同一类型物品对方案的贡献。由于数据范围较小,可以直接用三维遍历朴素解法。
示例代码
signed main() {
IOS;
int n, m;
cin >> n >> m;
vector<int> a(n + 1);
fer(i, 1, n + 1) cin >> a[i];
vector<int> dp(m + 1);
dp[0] = 1;
fer(i, 1, n + 1) {
ferd(j, m, 1) {
fer(k, 1, a[i] + 1) {
if(j >= k) dp[j] = (dp[j] + dp[j - k]) % MOD;
}
}
}
cout << dp[m] << '\n';
return 0;
}
P2347 砝码称重
题意
设有 1g、2g、3g、5g、10g、20g 的砝码各若干枚(其总重 ≤1000),可以表示成多少种重量?
思路
是可行性分析的多重背包问题。通过得知各种砝码的个数得到背包最大容量,然后二进制优化成01背包进行 \(dp\) 即可。
注意
不可以直接用某个常数(比如1000)替代背包的最大容量,当砝码总重远小于 1000 时,多余的 dp 状态(即 j 大于 maxN 的部分)根本不应该更新,却仍然参与了状态转移,可能导致错误的状态传递和计数。
示例代码
signed main() {
IOS;
vector<int> w(N);
int cnt = 0;
int maxN = 0;
fer(i, 1, 7) {
int v;
if(i <= 3) v = i;
else if(i == 4) v = 5;
else if(i == 5) v = 10;
else if(i == 6) v = 20;
int c;
cin >> c;
maxN += v * c;
int k = 1;
while(k <= c) {
w[++cnt] = k * v;
c -= k;
k <<= 1;
}
if(c) {
w[++cnt] = k * v;
c -= k;
}
}
// dp[j]: 重量j的可能性
vector<bool> dp(maxN + 1);
dp[0] = 1;
fer(i, 1, cnt + 1) {
ferd(j, maxN, w[i]) {
dp[j] = dp[j] | dp[j - w[i]];
}
}
int ans = 0;
fer(i, 1, maxN + 1) ans += dp[i];
cout << "Total=" << ans << '\n';
return 0;
}
P1776 宝物筛选
题意
二进制优化的多重背包的模板题。
思路
二进制优化模板题。
示例代码
signed main() {
IOS;
int n, W;
cin >> n >> W;
vector<int> a(N), b(N);
int cnt = 0;
fer(i, 0, n) {
int v, w, m;
cin >> v >> w >> m;
int k = 1;
while(k <= m) {
a[++cnt] = v * k;
b[cnt] = w * k;
m -= k;
k <<= 1;
}
if(m) {
a[++cnt] = v * m;
b[cnt] = w * m;
}
}
vector<int> dp(W + 1);
fer(i, 1, cnt + 1) {
ferd(j, W, b[i]) {
dp[j] = max(dp[j], dp[j - b[i]] + a[i]);
}
}
cout << dp[W] << '\n';
return 0;
}
P1782 旅行商的背包
题意
物品有两大种,一种是每种物品有对应的体积、价值、个数;另一种是“奇货”,第 i 件的价值 \(Yi\) 与分配的体积 \(Xi\) 之间的关系为:\(Yi=aiXi2+biXi+ci\) 。求背包容量为 \(C\) 的最大价值。
思路
前一部分显然是多重背包,后一部分其实是一个朴素的 \(dp\) 问题,先枚举背包容量,在枚举考虑的奇货的体积。
注意 !
对于奇货的 \(dp\) ,可以先枚举奇货体积,再枚举背包容量吗?
答案是不能的!
如果这样的话,会导致在更新过程中会用到已经更新过的 dp 值,导致同一个“奇货”被“重复使用”了(即多次分配体积),从而破坏了 0/1 背包的正确性。
而原来的遍历方式,先遍历了背包的容量,再遍历奇货体积,保证了对当前奇货的决策只基于上一个阶段(即还未加入当前奇货)的 dp 状态。这样就能正确地选择对当前奇货只做一次分配,从而避免重复使用。
示例代码
signed main() {
IOS;
int n, m, C;
cin >> n >> m >> C;
vector<int> V(N), W(N);
vector<ll> dp(C + 1);
int cnt = 0;
fer(i, 0, n) {
int v, w, d;
cin >> v >> w >> d;
int k = 1;
while(k <= d) {
V[++cnt] = k * v;
W[cnt] = k * w;
d -= k;
k <<= 1;
ferd(j, C, V[cnt]) {
dp[j] = max(dp[j], dp[j - V[cnt]] + W[cnt]);
}
}
if(d) {
V[++cnt] = d * v;
W[cnt] = d * w;
ferd(j, C, V[cnt]) {
dp[j] = max(dp[j], dp[j - V[cnt]] + W[cnt]);
}
}
}
fer(i, 0, m) {
int a, b, c;
cin >> a >> b >> c;
// fer(v, 1, C + 1) {
// ll w = 1LL * a * v * v + b * v + c;
// ferd(j, C, v) {
// dp[j] = max(dp[j], dp[j - v] + w);
// }
// }
ferd(j, C, 0) {
fer(v, 0, j + 1) {
ll w = 1LL * a * v * v + b * v + c;
dp[j] = max(dp[j], dp[j - v] + w);
}
}
}
cout << dp[C] << '\n';
return 0;
}

浙公网安备 33010602011771号