一、01背包
有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大——这就是标准的背包问题。
每一件物品只有两个状态,取或者不取,用0和1表示,就成了01背包。解决背包问题的基本思路分为三步:(这里不考虑暴力解法)
- 确定状态变量(函数)
- 确定状态转移方程(递推关系)
- 确定边界
具体实例如下。
物品编号 | 1 | 2 | 3 | 4 |
重量 | 2 | 3 | 4 | 5 |
价值 | 3 | 4 | 5 | 8 |
对于本题,解决思路如下:
1.确定状态变量
创建状态变量dp[i][j],表示前i件物品放入容量为j的背包所能取到的最大价值。
2.建立递推关系
物品i/容量j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | ||||||||
2 | 0 | ||||||||
3 | 0 | ||||||||
4 | 0 |
、
当背包容量为0时,所能取到的最大价值均为0;当物品编号为0(即未放入物品时),所能取到的最大价值也均为0.
物品i/容量j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
2 | 0 | ||||||||
3 | 0 | ||||||||
4 | 0 |
现在i变为1,即开始放入编号为1的物品。由于其重量为2,只有背包容量大于等于2时才会产生价值(为3)。
物品i/容量j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
2 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
3 | 0 | ||||||||
4 | 0 |
现在i变为2,即同时放入编号为1(w=2)和2(w=3)的物品。只有在背包容量大于等于3时,才可以放入编号2的物品;大于等于5时,才可以同时放入比编号1和2的物品。
下面开始建立递推关系。
分为两种情况:
1.背包当前容量小于第i件物品的重量
此时第i件物品无法放入,那么在背包容量为j的情况下,此时背包能装下的最大容量就是放入第i-1件物品的情况,即dp[i][j]=dp[i-1][j]。
2.背包当前容量大于等于第i件物品的重量。
此时我们可以装入第i件物品,很自然地,我们会考虑装入第i件物品是赚还是不赚,即比较装入与不装入第i件物品,哪个选择带来的利益更大。
如果选择不装入第i件物品,则此时dp[i][j]=dp[i-1][j];
反之,dp[i][j]=dp[i-1][j-w[i]]+v[i],即,我们选择要装入第i件物品,就会占据w[i]的空间,式子的含义是在减去占据空间的前提下,装入第i-1件物品所能获得的最大价值,再加上第i件物品的价值(装入第i件物品)。
由此,公式得出,dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])。
物品i/容量j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
2 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
3 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 9 |
4 | 0 | 0 | 3 | 4 | 5 | 8 | 8 | 9 | 12 |
最后,我们输出dp[N][W],也就是在装入N件物品、背包容量为W的情况下所能携带的最大利润。
3.具体代码实现
#include <iostream>
using namespace std;
#define N 4 // 物品容量
#define W 8 // 背包容量
int w[N+1] = {0,2,3,4,5}; // 重量
int v[N+1] = {0,3,4,5,8}; // 价值
int dp[N+1][W+1]; // 在携带第i件物品的容量为j的情况下背包的最大价值
int main()
{
for(int i = 1; i <= N; i++)
{ // 第i件物品
for(int j = 1; j <=W; j++)
{
if(j < w[i])
{ // 背包当前容量小于第i件物品的重量
dp[i][j] = dp[i-1][j];
}
else
{ // 背包当前容量大于等于第i件物品的重量
dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]] + v[i]);
}
}
}
cout << dp[N][W];
return 0;
}
注:此部分参考博客【0-1背包问题 】详细解析+图解+详细代码-优快云博客。
二、01背包推广
既然是推广,那么大体思路都是一致的。多了一个限制条件,我们就多设一个维度的数组。
设dp[i][j][k]表示在考虑前i种物品,背包重量限制为j,体积限制为k时,可以获得的最大价值。
按照跟01背包类似的思路,可以总结出以下几个步骤来解决这个问题:
1.初始化数组
在没有物品以及背包容量为0时,显然其最大价值也为0,即dp[0][j][k](没有物品)= dp[i][0][0](容量为0)= 0。
2.分情况讨论
对于每个物品i来说(i从1到n),对于每个可能的背包重量j(j从0到W),对于每个可能的背包体积k(k从0到V),我们考虑两种情况,即装入与不装入。
1.不装入第i件物品
在重量与体积条件保持不变的情况下(即还是j和k),最大价值就是装入第i-1件物品时的价值,即dp[i][j][k]=dp[i-1][j][k]。
2.装入第i件物品
分为装得下和装不下。
如果装得下,需要满足两个条件,即重量wi不超过j,体积ci不超过k,同时还需要跟不装第i件物品时的最大价值进行比较(详细见01背包),即dp[i][j][k]=max(dp[i-1][j][k],dp[i-1][j-wi][k-ci]+vi)。
如果装不下,那么最大价值就与不装入一样,即dp[i][j][k]=dp[i-1][j][k]。
3.输出答案
在经过三层循环遍历后,最终dp[n][W][V]就是我们求得的最大价值。
下面给出具体的代码实现。
#include<iostream>
using namespace std;
#define MAXN 100
#define MAXW 1000
#define MAXV 1000
// 假设最大物品数量为100,背包最大重量为1000,最大体积为1000
int w[MAXN], c[MAXN], v[MAXN]; // 重量,体积,价值
int dp[MAXN + 1][MAXW + 1][MAXV + 1]; // 定义dp数组
int main()
{
int n, W, V; // 物品数量,背包重量,背包体积
cin >> n >> W >> V; // 输入
for (int i = 1; i <= n; i++)
{
cin >> w[i] >> c[i] >> v[i];
} // 输入第i件物品的重量,体积和价值
for (int i = 1; i <= n; i++)
{ // 第i件物品
for (int j = 1; j <= W; j++)
{ // 重量
for (int k = 1; k <= V; k++)
{ // 体积
// 先对数组进行初始化
if (i == 0 || j == 0 || k == 0)
{ // 物品件数,背包重量,背包体积有一个为0,其最大价值就是0
dp[i][j][k] = 0;
}
// 再开始装入物品
// 如果装得下
else if (w[i] <= j && c[i] <= k)
{ // 重量不大于背包重量,体积不大于背包体积
dp[i][j][k] = max(dp[i - 1][j][k], dp[i - 1][j - w[i]][k - c[i]] + v[i]);
// 比较是装还是不装,哪个价值更高
}
// 如果装不下
else
{
dp[i][j][k] = dp[i - 1][j][k];
}
}
}
}
// 循环完毕,输出结果
cout << dp[n][W][V];
return 0;
}