1. 01背包
有 n 件物品和容量为 m 的背包,给出 n 件物品的重量 w[i] 以及价值 c[i] ,求解让装入背包的物品重量不超过背包容量且价值最大,每个物品只能选择一次 。
暴力解法:暴力枚举每件物品要不要放入背包,时间复杂度O(2^n),显然是不能接受的,而使用 DP 可以将时间复杂度变成O(nm)。
动态规划:用 dp[i][j] 表示,前 i 件物品放入容量为 j 的背包中所能获得的最大价值,很明显最终的 dp[n][m] 即为所求的结果,那么怎么求解 dp[i][j] 呢?
- 不放第 i 件物品,问题转换为前 i - 1 件物品放入容量为 j 的背包中所能获得的最大价值,即 dp[i][j] = dp[i - 1][j] 。
- 放第 i 件物品,问题转换为前 i - 1 件物品放入容量为 j - w[i] 的背包中所能获得的最大价值,即 dp[i][j] = dp[i - 1][j - w[i]] 。
- 要求最大价值,则 dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - w[i]] + v[i])
- 求出背包中最终装的物品。
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int MAX_N = 1e3;
const int MAX_M = 1e4;
int dp[MAX_N][MAX_M];
int w[MAX_N];
int v[MAX_N];
vector<int> choice;
int n, m;
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i){
scanf("%d", &w[i]);
}
for(int i = 1; i <= n; ++i){
scanf("%d", &v[i]);
}
//0-1背包问题,对于每一个物品
//用dp数组中每一项dp【j】表示循环中的前i件物品,在容量为j的背包中的最大价值
//用双层循环,构建一个前i件物品、容量0-m的表,最终表的右下角就是问题的答案
//可以为了节省空间,省略了二维数组的行,因为每一次多一个物品之后,答案只会受前一行影响
//先将边界,0件物品时候的价值设为0,因为要求所选择的物品,用二维数组
for(int i = 0; i <= m; ++i){
dp[0][i] = 0;
}
for(int i = 0; i <= n; ++i){
dp[i][0] = 0;
}
//循环构建表
//1.当前背包容量不够放该物品,不放
//2.当前容量够放该物品,看放不放的价值大小
//放的话,用背包容量减去该物品重量,回到前dp[i-1][j-w[i]] + v[i]
//比较放与不放的价值大小,选择大的价值的更新dp[i][j]
for(int i = 1; i <= n; ++i){
for(int j = m; j >= w[i]; --j){ //只考虑放的下的选择,
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
}
}
printf("0 - 1 package\n");
printf("the max value : %d\n", dp[n][m]);
//列举选择的每个物品
//1.先看该物品是不是被选择了,只需要看dp[i-1][j] == dp[i][j]
//2.再退到该物品选择前的物品的dp值处
int j = m;
for(int i = n; i > 0; --i){
if(dp[i - 1][j] != dp[i][j]){ //选择了该物品
choice.push_back(i);
j -= w[i]; //回到上一个背包重量
}
}
for(int i = choice.size() - 1; i >= 0; --i){
printf("%d ", choice[i]);
}
return 0;
}
2. 完全背包
有 n 件物品和容量为 m 的背包,给出 n 件物品的重量 w[i] 以及价值 c[i] ,求解让装入背包的物品重量不超过背包容量且价值最大,每个物品可以重复选择 。
下面思路与 01 背包问题相似,仅在放第i件物品处存在差异。
- 不放第 i 件物品,问题转换为前 i - 1 件物品放入容量为 j 的背包中所能获得的最大价值,即 dp[i][j] = dp[i - 1][j] 。
- 放第 i 件物品,问题转换为前 i 件物品放入容量为 j - w[i] 的背包中所能获得的最大价值,即 dp[i][j] = dp[i][j - w[i]] 。
- 要求最大价值,则 dp[i][j] = max(dp[i - 1][j],dp[i][j - w[i]] + v[i])
- 求出背包中最终装的物品。
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int MAX_N = 1e3;
const int MAX_M = 1e4;
int dp[MAX_N][MAX_M];
int w[MAX_N];
int v[MAX_N];
vector<int> choice;
int n, m;
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i){
scanf("%d", &w[i]);
}
for(int i = 1; i <= n; ++i){
scanf("%d", &v[i]);
}
//与0-1背包不同的是,每个物品可以任意多次,当选择第i件物品,dp[i][j] = dp[i][j - w[i]] + c[i]
for(int i = 1; i <= n; ++i){
for(int j = w[i]; j <= m; ++j){
dp[i][j] = max(dp[i - 1][j], dp[i][j - w[i]] + v[i]);
}
}
printf("\nentire package\n");
printf("the max value : %d\n", dp[n][m]);
j = m;
for(int i = n; i > 0; --i){
while(dp[i - 1][j] != dp[i][j]){ //选择了该物品
choice.push_back(i);
j -= w[i]; //回到上一个背包重量
}
}
for(int i = choice.size() - 1; i >= 0; --i){
printf("%d ", choice[i]);
}
return 0;
}
上面背包问题求解在空间上还可以优化,因为每次的第 i 行只与第 i - 1 行的数据有关,可以去掉dp数组的第一维度,但是这样背包中选择的物品的序号就不能得到了。