01背包
问题:给定n中物品和一个容量为c的背包,物品i的重量的w[i],其价值为v[i]。问:如何选择装入背包的物品,使得装入背包中的物品的总价值最大?
分析:面对物品,我们只有选择拿与不拿两种选择,不能选择装入某物品的一部分,也不能装入同一物品多次。
解释:为什么叫 01背包 ?拿0、不拿1。
先声明一个大小为 dp[i][j] 数组,表示从下标为 [0-i] 的物品里任意取,放进容量为j的背包,价值总和最大是多少。
注:i 表示第几个物品,j 表示背包的总容量。
状态转移方程:
状态转移表:
代码:
#include <iostream>
using namespace std;
int main() {
/**
* dp[i][j]: 状态转移方程
* w[]: 每个物品的重量
* v[]: 每件商品对应的价值
*/
int dp[30][200];
int w[30], v[30];
int n, m; //n:商品数量,m:背包容量
cin >> n >> m;
//初始值从0开始下标为负值
for (int i = 1; i <= n; ++i) {
cin >> w[i] >> v[i];
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (j < w[i])
dp[i][j] = dp[i-1][j];
else //j>=w[i]
dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
}
}
//=========打印状态转移表===========
for (int i = 0; i <=n; ++i) {
for (int j = 0; j <=m; ++j) {
cout << dp[i][j] << " ";
}
cout << endl;
}
cout<<"max value:" << dp[n][m];
return 0;
}
滚动数组改进:
因为当前状态只与上一个状态有关有关,所以我们可以使用滚动数组。
每当 j 值更新一遍 dp[j] 就滚动更新一遍,所以把 dp[j] 称为 滚动数组
int dp[205], w[35], v[35];
int main() {
int n, m; //n:商品数量,m:背包容量
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> w[i] >> v[i];
}
for (int i = 1; i <= n; ++i) {
for (int j = m; j >= 1; --j) { //???此处为什么逆序循环???
if (j >= w[i])
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
//打印状态转移表
for (int k = 0; k <=m; ++k)
cout << dp[k] << " ";
cout << endl;
}
cout << endl << "max value:" << dp[m];
return 0;
}
对逆序循环解析:
本质:后无效性原则,即当前的状态只与上一个状态有关。
dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
解释:如果 j 是顺序循环,dp[j-w[i]] 会先于 dp[j] 更新,也就是 用新值dp[j-w[i]] 去更新 dp[j] 所以出错。j 如果是逆序循环,dp[j] 先与 dp[j-w[i]] 更新,也就是 用旧值 dp[j-w[i]] 更新 dp[j] ,相当于用上一行的 dp[j-w[i]] 更新 f[j] 正确。