1、01背包问题
问题描述:
给定物体数量N,以及背包能够装下的最大重量V,对于物品i, 其重量为 weight[i],价值为value[i]。每种物品最多只能拿一次,求在不超过背包重量的前提下,能够拿到的物品的总价值的最大值是多少?
解决思路:
- 1、确定dp数组下标的含义
- 2、确定递推公式
- 3、初始化dp数组
- 4、确定遍历顺序
(1)确定dp数组下标的含义
- 用二维
dp[i][j]
来表示,在前i
件物品中取不超过重量为j
的最大价值
(2)确定递推公式
- 当第
i
件物品的重量大于j
时,即weight[i] > j
时,此时不能拿第i
件物品。故dp[i][j] = dp[i-1][j]
- 当第
i
件物品的重量小于等于j
时,即weight[i] <= j
时,此时可以拿也可以不拿。若拿第i
件物品,则dp[i][j] = dp[i-1][j - weight[i]] + value[i]
,若不拿,则dp[i][j] = dp[i-1][j]
。由于题目要求拿价值最大的,故在这两个当中取较大的那个。
递推公式如下:
(3)初始化dp数组
当 i = 0
时,即只拿第一个物品时,如果j<weight[0]
,则dp[i][j] = 0
;否则dp[0][j] = value[i]
。
初始化代码如下:
vector<vector<int>>dp(N, vector<int>(V+1)); //N为物品数量,V为背包能装的最大重量
for(int j = 0; j <= V; j++)
{
if(j >= weight[0])
{
dp[0][i] = weight[0];
}
}
(4)确定遍历序列
先遍历物品数量,再遍历背包容量
代码如下:
//先遍历物品数量,再遍历背包容量
for(int i = 0; i < N; i++) //遍历物品数量
{
for(int j = V; j >= 0; j++) //遍历背包容量
{
if(j >= weight[i])
{
dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]);
}
else
{
dp[i][j] = dp[i-1][j];
}
}
}
注意:在上述代码中,遍历背包容量时采用倒序是因为,每种物品只能拿一次。
具体原因:
如果遍历背包容量时采用正序,那么我们可以知道,i++
之后,dp[i][j]
又重复拿了之前拿过的物品。
完整代码如下:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int N, V;
cin >> N >> V;
vector<int>weight(N);
vector<int>value(N);
for(int i = 0; i < N; i++)
{
cin >> weight[i] >> value[i];
}
vector<vector<int>>dp(N, vector<int>(V+1));
//初始化dp数组
for(int j = 0; j <= V; j++)
{
if(j >= weight[0])
{
dp[0][i] = weight[0];
}
}
//先遍历物品数量,再遍历背包容量
for(int i = 0; i < N; i++) //遍历物品数量
{
for(int j = V; j >= 0; j--) //遍历背包容量
{
if(j >= weight[i])
{
dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]);
}
else
{
dp[i][j] = dp[i-1][j];
}
}
}
cout << dp[N-1][V] << endl;
return 0;
}
优化:对于01背包问题,时间复杂度为O(VN),已经无法优化。但是空间复杂度可以从O(VN)优化为O(V)。
具体解决流程同上。
(1)确定dp数组下标含义
dp[j]
表示背包容量为j
能够装的最大价值
(2)确定递推公式
- 若
j < weight[i],
则该物品不能拿,dp[j] = dp[j];
- 若
j >= weight[i]
,则可以选择拿该物体,或者不拿该物体
若当前物品不拿,则dp[j] = dp[j];
若当前物品拿的话,则dp[j] = dp[j-weight[i]] + value[i];
所以dp[i][j]
的递推公式如下:
(3)初始化dp数组
当背包容量为0时,可以拿的最大价值为0,所以初始化dp[0] = 0
(4)确定遍历顺序
必须先遍历物品种类,再遍历背包容量,且背包容量遍历顺序为倒序遍历
代码如下:
//只能先遍历物品种类,再遍历背包容量(且背包容量只能倒序遍历)
for(int i = 0; i < N; i++) //遍历物品种类
{
for(int j = V; j >= 0; j--) //遍历背包容量
{
if(j >= weight[i])
{
dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
}
else
{
dp[j] = max(dp[j],dp[j - weight[i]] + value[i]);
}
}
}
完整代码如下:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int N, V;
cin >> N >> V;
vector<int>weight(N);
vector<int>value(N);
for(int i = 0; i < N; i++)
{
cin >> weight[i] >> value[i];
}
vector<int>dp(V+1,0);
//初始化dp数组
dp[0] = 0;
//只能先遍历物品种类,再遍历背包容量(且背包容量只能倒序遍历)
for(int i = 0; i < N; i++) //遍历物品种类
{
for(int j = V; j >= 0; j--) //遍历背包容量
{
if(j >= weight[i])
{
dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
}
else
{
dp[j] = dp[j];
}
}
}
cout << dp[V] << endl;
return 0;
}
2、完全背包问题
问题描述:
给定物体数量N,以及背包能够装下的最大重量V,对于物品i, 其重量为 weight[i],价值为value[i]。每种物品可以拿无限多次,求在不超过背包重量的前提下,能够拿到的物品的总价值的最大值是多少?
解决思路:
对于完全背包问题,解决思路跟01背包问题几乎相同,唯一不同的地方在于,再遍历的背包容量的时候采用正序遍历。
代码如下:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int N, V;
cin >> N >> V;
vector<int>weight(N);
vector<int>value(N);
for(int i = 0; i < N; i++)
{
cin >> weight[i] >> value[i];
}
vector<int>dp(V+1,0);
//初始化dp数组
dp[0] = 0;
//只能先遍历物品种类,再遍历背包容量(且背包容量只能倒序遍历)
for(int i = 0; i < N; i++) //遍历物品种类
{
for(int j = 0; j <= V; j++) //遍历背包容量
{
if(j >= weight[i])
{
dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
}
else
{
dp[j] = dp[j];
}
}
}
cout << dp[V] << endl;
return 0;
}
3、多重背包问题
问题描述:
给定物体数量N,以及背包能够装下的最大重量V,对于物品i, 其重量为 weight[i],价值为value[i]。对于物品i,可以拿size[i]次。求在不超过背包重量的前提下,能够拿到的物品的总价值的最大值是多少?
解决思路:
对于多重背包问题,可以将其转化为01背包问题。
转化思路:
与01背包唯一不同的是,规定了每件物品最多能拿size[i]次,把size[i]件物品摊开,其实就是一个01背包问题了。相当于物品的数量增加了而已。
实现代码如下:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int N, V;
cin >> N >> V;
vector<int>weight(N); //物品重量
vector<int>value(N); //物品价值
vector<int>size(N); //物品件数
for(int i = 0; i < N; i++)
{
cin >> weight[i] >> value[i] >> size[i];
}
for(int i = 0; i< N; i++)
{
while(size[i] > 1) //把该物品展开,直到剩一件
{
weight.push_back(weight[i]);
value.push_back(value[i]);
size[i]--;
}
}
int n = weight.size();
vector<int>dp(V+1);
for(int i = 0; i < n; i++)
{
for(int j = V; j >= weight[i]; j--)
{
dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
}
}
cout << dp[V] << endl;
return 0;
}