问题描述
01背包问题是动态规划中的一个经典问题。它的本质是一个集合中选择若干元素,使得这些元素满足某种特定条件并且具有最大的价值。在背包问题中,我们要把一些物品(每个物品有自己的价值和体积)装入一个容量为m的背包中,问怎样装才能使背包的内物价值最大。这是一个经典的优化问题,可以通过动态规划来解决。
假设有n个物品和一个容量为m的背包。第i个物品的体积是Wi,价值是Vi。那么,最优解就是如下所述:在不超过背包容量的前提下,选出一些物品使得它们的总价值最大。
问题思路
解决这个问题的方法是采用动态规划思想。令dp[i][j]表示将前i个物品放入一个容量为j的背包中所得到的最大价值。则有如下状态转移方程:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]);
初始化:
对dp[i][j]而言,i或j为0时,价值都是0
递推关系式:
- j<w(i) V(i,j)=V(i-1,j)
- j>=w(i) V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}
第一个式子表示不选择第i个物品,第二个式子表示选择第i个物品。在选择第i个物品的情况下,背包容量要减去Wi,因此总价值要加上Vi。
最终结果
最终的答案就是dp[n][m],表示将n个物品放入一个容量为m的背包中所得到的最大价值。
代码实现:
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
int w[n+1], v[n+1];
for (int i = 1; i <= n; i++) {
cin >> w[i] >> v[i];
}
int dp[n+1][m+1];
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++) {
if (i == 0 || j == 0) {
dp[i][j] = 0;
} else if (j < w[i]) {
dp[i][j] = dp[i-1][j];
} else {
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]);
}
}
}
cout << dp[n][m] << endl;
return 0;
}
最终的答案就是dp[n][m],表示将n个物品放入一个容量为m的背包中所得到的最大价值。
最优解由哪些商品组成?
我们不妨看看上面是如何通过递推关系式求得了最优解:
V(i,j)=V(i-1,j) => 说明没有选择第i 个商品,则回到V(i-1,j);
V(i,j)=V(i-1,j-w(i))+v(i) => 说明装了第i个商品,它是最优解组成的一部分,然后我们回到装这个商品之前的状态,即V(i-1,j-w(i));一直遍历到i=0为止,找到了所有解的组成。
void find(int i, int j) { //最优解情况
if (i >= 0) {
if (dp[i][j] == dp[i - 1][j]) {
flag[i] = 0;//标记这个物品的编号为0,代表没有装入它
find(i - 1, j);
}
else if (j - w[i] >= 0 && dp[i][j] == dp[i - 1][j - w[i]] + v[i]) {
flag[i] = 1;
find(i - 1, j - w[i]);//标记这个物品的编号为1,代表装入了它
}
}
}
总之,01背包问题是动态规划中一个非常经典的问题。通过状态转移方程,我们可以得到dp数组。在代码实现中,我们需要注意一些边界问题,比如数组下标的起点等等。选择好状态转移方程,就可以利用动态规划的思想来解决这个问题。