最近刷leetcode,遇到背包问题基本上都不会,找了个视频学习一下。
b站链接:https://www.bilibili.com/video/BV1C7411K79w?from=search&seid=17723915011352706427
1、0/1背包问题
给定n种物品和一个容量为C的背包,物品i的重量是wi,其价值为vi。
问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?
问题分析:面对每个物品,我们只有选择拿取或者不拿两种选择,不能选择装入某物品的一部分,也不能装入同一物品多次。
我们声明先一个大小为f[n][c]的二 维数组,f[i][j]表示在面对第i件物品,且背包容量为j时所能获得的最大价值。
例如这样一个问题:
【题目描述】
一个旅行者有一个最多能装M公斤的背包,现在有n件物品,它们的重量分别是W1, W2, ...Wn它们的价值分别为C1,C2....Cn ,求旅行者能获得最大总价值。
【输入】
第一行:两个整数, M(背包容量, M≤200)和N(物品数量,N≤30) ;
第2..N+ 1行;每行二个整数Wi,Ci,表示每个物品的重量和价值。
【输出】
仅一行,一个数,表示最大总价值。
【输入样例】
10 4
2 1
3 3
4 5
7 9
【输出样例】
12
设置一个dp[i][j]数组。i表示的是物品,j表示容量,dp[i][j]表示价值。
dp[1][0]则表示容量为0,拿第1个物品时,价值是多少。
......
dp[1][M]表示容量为M,拿第1个物品时,价值是多少。
.....
dp[N][M]表示容量为M,拿第N个物品时,价值是多少。
画表查看
|
| 重量W[i] | 价值C[i] |
背包容量(一个是10)j |
| ||||||||||
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| |||
i | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 注① |
| 1 | 2 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 注② |
| 2 | 3 | 3 | 0 | 0 | 1 | 3 | 3 | 4 |
|
|
|
|
| 注③ |
| 3 | 4 | 5 |
|
|
|
|
|
|
|
|
|
|
|
|
| 4 | 7 | 9 |
|
|
|
|
|
|
|
|
|
|
|
|
注①:第0个物品,无论放进多大的背包,价值都为0;
注②:第1个物品(重量为2,价值为1),只能放到背包容量大于等于2的背包;
注③:这一行的第一个数据1,背包容量为2,不足以放进第二件物品。只能不拿,这个1是上一个物品拿的价值,根据后无效原则,可以不管。j < w[i](当前物品容量大于背包容量),不足以放下,只能不拿。dp[i][j] = dp[i-1][j](上一种物品的价值);
这一行的第二个数据3,根据价值最大原则,拿的是第二件物品,价值为3;j > w[i](当前物品容量小于背包容量);
如果不拿,上一物品的价值:dp[i][j] = dp[i-1][j] = 1;如果拿了,容量应该减去当前的容量,并且价值加上当前价值。dp[i][j] = dp[i-1][j - w[i]] + c[i] = dp[1][0]+3 = 0 + 3 = 3;然后取较大值。状态转移方程式dp[i][j] = max(dp[i-1][j],dp[i-1][j - w[i]] + c[i]);
这一行的第三个数据3,dp[i][j] = max(dp[i-1][j],dp[i-1][j - w[i]] + c[i]);dp[i][j] = max(dp[2][4],dp[2][4-3] + 3) = max(3,0+3) = 3
......
//重点代码
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]] + c[i]);
}
完整代码
#include <iostream>
#include <algorithm>
using namespace std;
//01背包问题
int dp[35][205];/*dp[i][j]数组*/
int w[35]; /*重量数组*/
int c[35]; /*价值数组*/
int main(int argc, char* argv[])
{
int m, n; //m是背包重量,n是物品数量
cin >> m >> n;
//读入数据
for (int i = 1; i <= n; i++)
{
cin >> w[i] >> c[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
{
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + c[i]);
}
}
}
//输出dp表
for (int i = 0; i <= n; i++)
{
for (int j = 0; j <= m; j++)
{
cout << dp[i][j] << "\t";
}
cout << endl;
}
//只输出最终结果
cout << dp[n][m] << endl;
system("pause");
return 0;
}
后无效原则:当前原则只与上一个状态有关。
根据后无效原则,那是否可以压缩数组大小呢?
答案是可以的,把dp[][]数组设置成一维数组
【这里没有理解】
重点代码
//重点代码
if(j >= w[i])
{
//放的下
dp[j] = max(dp[j],dp[j-w[i]]+c[i])
}
完整代码
#include <iostream>
#include <algorithm>
using namespace std;
//01背包问题
int dp[205]; /*dp[j]数组*/
int w[35]; /*重量数组*/
int c[35]; /*价值数组*/
int main(int argc, char* argv[])
{
int m, n; //m是背包重量,n是物品数量
cin >> m >> n;
//读入数据
for (int i = 1; i <= n; i++)
{
cin >> w[i] >> c[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]] + c[i]);
}
}
//输出dp表
for (int k = 0; k <= m; k++)
{
cout << dp[k] << "\t";
}
cout << endl;
}
//只输出最终结果
cout << dp[m]<< endl;
system("pause");
return 0;
}
2、完全背包
代码
#include <iostream>
#include <algorithm>
using namespace std;
int dp[35]; // dp数组
int w[35]; // 重量数组
int c[35]; // 价值数组
// 朴素的算法 3重循环
void test01()
{
int m, n; // m是容量 n是数量
cin >> m >> n;
//输入数据
for (int i = 1; i <= n; i++)
{
cin >> w[i] >> c[i];
}
// 递推
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= 1; j--)
{
for (int k = 0; k <= j / w[i]; k++)
{
if (j >= w[i])
{
dp[j] = max(dp[j], dp[j - k*w[i]] + k*c[i]);
}
}
}
}
cout << dp[m] << endl;
}
// 优化的算法
void test02()
{
int m, n; // m是容量 n是数量
cin >> m >> n;
//输入数据
for (int i = 1; i <= n; i++)
{
cin >> w[i] >> c[i];
}
// 递推
for (int i = 1; i <= n; i++)
{
for (int j = w[i]; j<=m; j++) // 顺向 从能放下的开始推
{
dp[j] = max(dp[j], dp[j - w[i]] + c[i]);
}
}
cout << dp[m] << endl;
}
/*
测试数据
10 4
2 1
3 3
4 5
7 9
*/
int main(int argc, char** argv)
{
//test01();
test02();
system("pause");
return 0;
}
3、多重背包
问题描述:有N中物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值
是w[i]。求解将哪些物品装入背包可使这些物品的总费用之和不超过背包容量,且价值之和最大。
分析:01背包只有1件,完全背包是无限件,多重背包是每种有限件。
朴素的分析:把相同的物品分开,再用01背包的方法解决问题。
#include <iostream>
#include <algorithm>
using namespace std;
int v[510]; // 价格
int w[510]; // 价值
int s[510]; // 个数
int dp[6100]; // dp数组
/*
测试数据
5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1
1040
*/
void test01()
{
// n是个数,m是价格
int n, m;
cin >> n >> m;
// 分别输入价格、价值、数量
for (int i = 1; i <= n; i++)
{
cin >> v[i] >> w[i] >> s[i];
}
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= 1; j--)
{
for (int k = 0; k <= s[i] && k*v[i] <= j; k++)
{
// 从01背包的状态转移方程式,去增加i个物品拿k个的循环
dp[j] = max(dp[j], dp[j - k*v[i]] + w[i] * k);
}
}
}
cout << dp[m] << endl;
}
int main(int argc, char** argv)
{
test01();
system("pause");
return 0;
}