更好的阅读体验
\color{red}{更好的阅读体验}
更好的阅读体验
3.1 模板题
多重背包问题 I
描述
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<vi,wi,si≤100
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
输出样例:
10
思想
- 状态表示:
- 集合:
dp[i][j]
表示前i
个物品,总体积不超过j
的价值 - 属性:最大价值
- 集合:
- 状态计算:
- 不选第
i
个 物品:dp[i][j] = dp[i - 1][j]
- 选第
i
个物品k
个:dp[i][j] = dp[i - 1][j - k * v[i]] + k * w[i]
- 集合属性为最大价值,故两种情况取
max()
- 不选第
- 当背包容量不够或者物品数量不够时,即
j < k * v[i] || k > s[i]
时,不能选择物品,反之可选
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int dp[N][N];
int v[N], w[N], s[N];
void solve(){
int n, m;
cin >> n >>m;
for(int i = 1; i <= n; i ++) cin >> v[i] >> w[i] >> s[i];
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
for(int k = 0; k * v[i] <= j && k <= s[i]; k ++){ //枚举选择物品的个数
dp[i][j] = max(dp[i][j],dp[i - 1][j - k * v[i]] + k * w[i]);
}
}
}
cout << dp[n][m] << endl;
}
int main(){
solve();
return 0;
}
多重背包问题 II
描述
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V≤2000
0<vi,wi,si≤2000
输出样例:
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
思想
- 由于 S S S范围大,不可能逐一枚举,故采用二进制优化
- 对于一组数
1
,
2
,
4
,
8
,
16
,
32
1,2,4,8,16,32
1,2,4,8,16,32,每个数只使用一次,可以凑成如下的数:
- 用 1 , 2 1,2 1,2可以凑出 3 3 3,即 1 , 2 1,2 1,2可凑出 1 ∼ 3 1\sim 3 1∼3之间的任意数
- 则用 1 , 2 , 4 1,2,4 1,2,4可以凑出 1 ∼ 7 1\sim 7 1∼7之间的任意数
- 则 1 , 2 , 4 , 8 1,2,4,8 1,2,4,8可以凑出 1 ∼ 15 1\sim 15 1∼15之间的任意数
- 则 1 , 2 , 4 , 8 , 16 1,2,4,8,16 1,2,4,8,16可以凑出 1 ∼ 31 1\sim 31 1∼31之间的任意数
- 则 1 , 2 , 4 , 8 , 16 , 32 1,2,4,8,16,32 1,2,4,8,16,32可以凑出 1 ∼ 63 1\sim 63 1∼63之间的任意数
- 综上,使用 2 0 , 2 1 , 2 2 , … , 2 n 2^0,2^1,2^2,\dots,2^n 20,21,22,…,2n可以凑出 1 ∼ 2 n + 1 − 1 1\sim 2^{n+1}-1 1∼2n+1−1之间的任意整数
- 对于一个数
17
17
17,若要用上述方法凑出
1
∼
17
1\sim 17
1∼17之间的任意数:
- 可选 1 , 2 , 4 , 8 1,2,4,8 1,2,4,8,每个数只用一次即可凑出 1 ∼ 15 1\sim 15 1∼15
- 由于最大只能凑到 15 15 15,故需添加一个数 2 ( 17 − 15 ) 2(17-15) 2(17−15),来凑到 17 17 17
- 即用 1 , 2 , 4 , 8 , 2 1,2,4,8,2 1,2,4,8,2即可凑出 1 ∼ 17 1\sim 17 1∼17之间的任意一个数
- 对于第
i
个物品,体积为v[i]
,价值为w[i]
,共有s[i]
件 - 由二进制优化,我们可以将
s[i]
利用二进制拆分,用 1 , 2 , 4 , … 1,2,4,\dots 1,2,4,…凑出s[i]
- 即我们将物品
i
,拆分成了:- 一件体积为
1 * v[i]
的物品,价值为1 * w[i]
的捆绑物品 - 一件体积为
2 * v[i]
的物品,价值为2 * w[i]
的捆绑物品 - 一件体积为
4 * v[i]
的物品,价值为4 * w[i]
的捆绑物品 - … \dots …
- 一件体积为
- 由拆分出的这些新的捆绑物品,我们可以凑出
1
∼
s
[
i
]
1\sim s[i]
1∼s[i]之间的任意数目的
i
物品,不必逐一枚举进行选择 - 由二进制拆分,原有的每个物品数量 S S S可以降为 l o g 2 S log_2^S log2S,时间复杂度大幅降低
- 对于最终求解,可以采用01背包的一维优化求解
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int dp[N];
int cnt; //用于记录新的捆绑物品的下标
int v[N], w[N];
void solve(){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i ++){
int a, b, s;
cin >> a >> b >> s;
int k = 1; //k从1开始捆绑
while(k < s){
cnt ++;
v[cnt] = k * a; //捆绑物品的体积
w[cnt] = k * b; //捆绑物品的价值
s -= k;
k <<= 1;
}
if(s){ //最后剩余的无法凑整的数
cnt ++;
v[cnt] = s * a;
w[cnt] = s * b;
}
}
for(int i = 1; i <= cnt; i ++){ //01背包一维优化求解
for(int j = m; j >= v[i]; j --){
dp[j] = max(dp[j],dp[j - v[i]] + w[i]);
}
}
cout << dp[m] << endl;
}
int main(){
solve();
return 0;
}