背包九讲练习题
01背包
有N种物品和一个容量为V的背包,每种物品只有1个,第i种物品的体积为v[i],价值为w[i]。问将哪些物品装入背包,可使总体积不超过背包容量,且总价值最大,输出最大值。
0<N,V<=1000; 0<v[i],w[i]<=1000
分析:标准01背包,每样物品按选和不选进行转移,时间复杂度O(NV),这里使用滚动数组优化空间复杂度。
注意,01背包一般枚举容量,即指定容量能取到的最大价值;如果容量很大,可以枚举价值,即指定价值需要的最小容量。
#include <bits/stdc++.h>
int main() {
int N, V;
std::cin >> N >> V;
std::vector<int> v(N), w(N);
for (int i = 0; i < N; i++) {
std::cin >> v[i] >> w[i];
}
std::vector<int> dp1(V + 1), dp2(V + 1);
for (int i = 0; i < N; i++) {
for (int j = 0; j <= V; j++) {
dp2[j] = dp1[j];
if (j >= v[i]) {
dp2[j] = std::max(dp2[j], w[i] + dp1[j-v[i]]);
}
}
std::swap(dp1, dp2);
}
std::cout << dp1[V] << "\n";
return 0;
}
完全背包
有N种物品和一个容量为V的背包,每种物品有无限多个,第i种物品的体积为v[i],价值为w[i]。问将哪些物品装入背包,可使总体积不超过背包容量,且总价值最大,输出最大值。
0<N,V<=1000; 0<v[i],w[i]<=1000
分析:每件物品按选和不选进行转移,跟01背包的区别在于同一种物品可以选多个。
#include <bits/stdc++.h>
int main() {
int N, V;
std::cin >> N >> V;
std::vector<int> v(N), w(N);
for (int i = 0; i < N; i++) {
std::cin >> v[i] >> w[i];
}
std::vector<int> dp1(V + 1), dp2(V + 1);
for (int i = 0; i < N; i++) {
for (int j = 0; j <= V; j++) {
dp2[j] = dp1[j];
if (j >= v[i]) {
dp2[j] = std::max(dp2[j], w[i] + dp2[j-v[i]]);
}
}
std::swap(dp1, dp2);
}
std::cout << dp1[V] << "\n";
return 0;
}
多重背包之朴素版本
有N种物品和一个容量为V的背包,第i种物品的体积为v[i],价值为w[i],个数为s[i]。问将哪些物品装入背包,可使总体积不超过背包容量,且总价值最大,输出最大值。
0<N,V<=100; 0<v[i],w[i],s[i]<=100
分析:枚举每种物品选0个,选1个,选2个...,分别进行转移。
#include <bits/stdc++.h>
int main() {
int N, V;
std::cin >> N >> V;
std::vector<int> v(N), w(N), s(N);
for (int i = 0; i < N; i++) {
std::cin >> v[i] >> w[i] >> s[i];
}
std::vector<int> dp1(V + 1), dp2(V + 1);
for (int i = 0; i < N; i++) {
for (int j = 0; j <= V; j++) {
dp2[j] = dp1[j];
for (int k = 1; k <= s[i] && j >= k * v[i]; k++) {
dp2[j] = std::max(dp2[j], k * w[i] + dp1[j-k*v[i]]);
}
}
std::swap(dp1, dp2);
}
std::cout << dp1[V] << "\n";
return 0;
}
多重背包之倍增优化
题面与上面相同,数据有加强。
0<N<=1000; 0<V<=2000; 0<v[i],w[i],s[i]<=2000
分析:将每种物品按2进制合成,转换成01背包问题,时间复杂度O(NVlogS),其中S为物品总个数。
#include <bits/stdc++.h>
int main() {
int N, V;
std::cin >> N >> V;
std::vector<int> v(N), w(N), s(N);
for (int i = 0; i < N; i++) {
std::cin >> v[i] >> w[i] >> s[i];
}
std::vector<int> nv, nw;
for (int i = 0; i < N; i++) {
for (int j = 1; j < s[i]; j *= 2) {
nv.push_back(j * v[i]);
nw.push_back(j * w[i]);
s[i] -= j;
}
nv.push_back(s[i] * v[i]);
nw.push_back(s[i] * w[i]);
}
int n = nv.size();
std::vector<int> dp1(V + 1), dp2(V + 1);
for (int i = 0; i < n; i++) {
for (int j = 0; j <= V; j++) {
dp2[j] = dp1[j];
if (j >= nv[i]) {
dp2[j] = std::max(dp2[j], nw[i] + dp1[j-nv[i]]);
}
}
std::swap(dp1, dp2);
}
std::cout << dp1[V] << "\n";
return 0;
}
多重背包之单调队列优化
题面与上面相同,数据有加强。
0<N<=1000; 0<V<=20000; 0<v[i],w[i],s[i]<=20000
分析:
1、根据朴素版本的状态转移方程可知,计算dp[j]时,由dp[j-v],dp[j-2v],dp[j-3v]...转移而来,这些数模v同余,因此可以按照模v的余数分组计算。
2、假设余数为a(0<=a<v),那么依次计算dp[a], dp[a+v], dp[a+2v]...
3、原始的状态转移方程系数有差异,不方便维护,以s=3为例,状态转移如下:
dp[a+4v] <- dp[a+3v]+w, dp[a+2v]+2w, dp[a+1v]+3w
dp[a+5v] <- dp[a+4v]+w, dp[a+3v]+2w, dp[a+2v]+3w
dp[a+6v] <- dp[a+5v]+w, dp[a+4v]+2w, dp[a+3v]+3w
稍微转换下形式,就可以变成统一的式子,用单调队列来维护最大值。
dp[a+4v]-4w <- dp[a+3v]-3w, dp[a+2v]-2w, dp[a+1v]-1w
dp[a+5v]-5w <- dp[a+4v]-4w, dp[a+3v]-3w, dp[a+2v]-2w
dp[a+6v]-6w <- dp[a+5v]-5w, dp[a+4v]-4w, dp[a+3v]-3w
4、由于卡常数,加速了输入,并用数组代替stl。
#include <bits/stdc++.h>
struct Node {
int cnt, val;
};
Node q[20005];
int v[1005], w[1005], s[1005], dp[20005];
int main() {
std::cin.tie(0)->sync_with_stdio(0);
int N, V;
std::cin >> N >> V;
for (int i = 0; i < N; i++) {
std::cin >> v[i] >> w[i] >> s[i];
}
for (int i = 0; i < N; i++) {
for (int j = 0; j < v[i]; j++) {
int L = 0, R = 0;
for (int k = j, z = 0; k <= V; k += v[i], z += 1) {
while (L < R && q[L].cnt < z - s[i]) {
L += 1;
}
while (L < R && q[R-1].val < dp[k] - z * w[i]) {
R -= 1;
}
q[R++] = {z, dp[k] - z * w[i]};
dp[k] = q[L].val + z * w[i];
}
}
}
std::cout << dp[V] << "\n";
return 0;
}
混合背包
有N种物品和一个容量为V的背包,第i种物品的体积为v[i],价值为w[i],个数为s[i]。问将哪些物品装入背包,可使总体积不超过背包容量,且总价值最大,输出最大值。
其中,s[i]为-1表示只有1个,为0表示有无数多个,其他值表示有s[i]个。
0<N,V<=1000; 0<v[i],w[i]<=1000; -1<=s[i]<=1000
分析:统一转换成01背包来做,对于完全背包,可以认为个数是V/v,因为再多了也无法装进背包。
#include <bits/stdc++.h>
int main() {
int N, V;
std::cin >> N >> V;
std::vector<int> nv, nw;
for (int i = 0; i < N; i++) {
int v, w, s;
std::cin >> v >> w >> s;
if (s == -1) {
s = 1;
} else if (s == 0) {
s = V / v;
}
for (int j = 1; j < s; j *= 2) {
nv.push_back(v * j);
nw.push_back(w * j);
s -= j;
}
nv.push_back(v * s);
nw.push_back(w * s);
}
int n = nv.size();
std::vector<int> dp1(V + 1), dp2(V + 1);
for (int i = 0; i < n; i++) {
for (int j = 0; j <= V; j++) {
dp2[j] = dp1[j];
if (j >= nv[i]) {
dp2[j] = std::max(dp2[j], nw[i] + dp1[j-nv[i]]);
}
}
std::swap(dp1, dp2);
}
std::cout << dp1[V] << "\n";
return 0;
}
二维费用背包
有N种物品和一个容量为V、最大承重为M的背包,每种物品只有1个,第i种物品的体积为v[i],重量为m[i],价值为w[i]。问将哪些物品装入背包,可使总体积和总重量都不超限,且总价值最大,输出最大值。
0<N<=1000; 0<V,M<=100; 0<v[i],m[i]<=100; 0<w[i]<=1000
分析:跟01背包类似,区别在于需要枚举两个维度,时间复杂度O(NVM)。
#include <bits/stdc++.h>
int main() {
int N, V, M;
std::cin >> N >> V >> M;
std::vector<int> v(N), m(N), w(N);
for (int i = 0; i < N; i++) {
std::cin >> v[i] >> m[i] >> w[i];
}
std::vector dp1(V + 1, std::vector<int>(M + 1));
std::vector dp2(V + 1, std::vector<int>(M + 1));
for (int i = 0; i < N; i++) {
for (int j = 0; j <= V; j++) {
for (int k = 0; k <= M; k++) {
dp2[j][k] = dp1[j][k];
if (j >= v[i] && k >= m[i]) {
dp2[j][k] = std::max(dp2[j][k], w[i] + dp1[j-v[i]][k-m[i]]);
}
}
}
std::swap(dp1, dp2);
}
std::cout << dp1[V][M] << "\n";
return 0;
}
分组背包
有N组物品和一个容量为V的背包,每组物品有若干个,同一组内的物品最多只能选1个,给出每件物品的体积v、价值w以及所属分组,问将哪些物品装入背包可以在不超容量的前提下价值最大,输出最大价值。
0<N,V<=100; 0<S[i]<=100; 0<v[i][j],w[i][[j]<=100
分析:依次遍历各个分组,对于分组i,可以全都不选,也可以选其中1个,因此枚举选择其中每个物品的情况。
#include <bits/stdc++.h>
struct Node {
int v, w;
};
int main() {
int N, V;
std::cin >> N >> V;
std::vector<std::vector<Node>> g(N);
for (int i = 0; i < N; i++) {
int cnt;
std::cin >> cnt;
g[i].resize(cnt);
for (int j = 0; j < cnt; j++) {
std::cin >> g[i][j].v >> g[i][j].w;
}
}
std::vector<int> dp1(V + 1), dp2(V + 1);
for (int i = 0; i < N; i++) {
for (int j = 0; j <= V; j++) {
dp2[j] = dp1[j];
for (int k = 0; k < g[i].size(); k++) {
if (j >= g[i][k].v) {
dp2[j] = std::max(dp2[j], g[i][k].w + dp1[j-g[i][k].v]);
}
}
}
std::swap(dp1, dp2);
}
std::cout << dp1[V] << "\n";
return 0;
}
浙公网安备 33010602011771号