从毕业到入职,忙于各种事情,所以博客一直也没有空更新。从入职到现在差不多整三个月了,刚刚在比亚迪这边转正。目前干的工作涉及到开发的工作不是很多,但是又怕之前的技能荒废了。所以最近有个想法,再把C++算法和数据结构的东西捡起来,练一练。
现在再做这些,反而少了一些功利心,可以单纯地去学一下算法和数据结构的东西。
先定个小目标:把华为机试这100多道算法题先干掉!然后记录一下其中中等难度及以上难度题目的解析。一方面激励自己,一方面也给自己的学习过程留个记录,后面再回顾的时候,也更方便些。
题目
题目解析
首先这个题目比较长,比较复杂,所以理解起来也稍微有点费劲,需要仔细去读,不然很容易在理解上产生错误和偏差。
关键点
- 王强要购买的物品分为两类,有主件和附件两种,附件不再有自己的附件。
- 每个主件可以有0个,1个或者2个附件,即最多一个主件可以有两个附件。
- 每个物品有一个重要度,用1~5表示。
- 单个物品的满意度 = 价格 * 重要度
最后题目要求要使王强得到最大的满意度,并且输出是多少。
从题目来看,该题目是0-1背包问题的一个扩展。
有关0-1背包问题的动态规划,下面这篇博客写得非常详细,我这里不再赘述。
【动态规划】01背包问题(通俗易懂,超基础讲解)
这里简单说一下0-1背包里面要解决的问题以及其中定义的一些变量,方便下面来进行比较。
如上图所示
- 这里背包的容量就相当于王强所拥有的总钱数N。
- 物体的体积或者说重量w相当于该题目中物品的价格。
- 而价值v相当于该题目中的满意度。
另外该问题与0-1背包问题的一个区别就是:
- 0-1背包问题对于物品要考虑的就是两种情况:
- 选
- 不选
- 该题目则是有五种情况:
- 不买
- 只买主件
- 买主件 + 附件1
- 买主件 + 附件2
- 买主件 + 附件1 + 附件2
所以,解这个题目需要先重新把这个表填了。
令v[i]表示第i个物品的价格:
- v[i][0] 表示物品是主件
- v[i][1] 表示物品是附件1
- v[i][2] 表示物品是附件2
令w[i]表示第i个物品的满意度:
- w[i][0] 表示物品是主件
- w[i][1] 表示物品是附件1
- w[i][2] 表示物品是附件2
令dp[i][j] 表示当前钱数为j,前i个物品最佳组合对应的价值。
状态转移方程
dp[i][j] = max(
dp[i-1][j] ,(不买第i件物品)
dp[i-1][j-w[i][0]]+v[i][0] ,(买第i件物品)
dp[i-1][j - w[i][0]-w[i][1]]+v[i][0]+v[i][1] , (买第i件物品与其附件1)
dp[i-1][j - w[i][0]-w[i][1]-w[i][2]]+v[i][0]+v[i][1]+v[i][2] ,(买第i件物品与其附件2)
dp[i-1][j-w[i][0]-w[i][1]-w[i][2]-w[i][3]]+v[i][0]+v[i][1]+v[i][3]).(买第i件物品与其两件附件)
需要注意的几个点:
- 物品编号从1开始
- 遍历顺序为先遍历背包,再遍历物品,这是因为有主件附件等不同情况的原因。
- 初始化的边界条件均为0,即dp[0][……]和dp[……][0]均为0
源代码
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main() {
int N, m;
cin >> N >> m;
vector<vector<int>> v(m + 1, vector<int>(3, 0)); // 存价格,相当于0-1背包中的W
vector<vector<int>> w(m + 1, vector<int>(3, 0)); // 存价格*重要度,即满意度,相当于0-1背包中的V
vector<vector<int>> dp(m + 1, vector<int>(N + 1, 0));
N /= 10;
for (int i = 1; i < m + 1; i++) { // 物品编号从1开始,q为0表示主件,为1表示第1件物品的附件
int vv, p, q;
cin >> vv >> p >> q;
vv /= 10; // 简化计算,加快计算速度
if (q) { // 如果q不为0,则说明第i个物品是附件
if (w[q][1]) { // 如果w[q][1]不为0,则说明不是第一个附件
v[q][2] = vv;
w[q][2] = vv * p;
} else { // 否则就是第一个附件
v[q][1] = vv;
w[q][1] = vv * p;
}
} else { // 否则,第i个物品是主件
v[i][0] = vv;
w[i][0] = vv * p;
}
}
for (int j = 1; j < N + 1; j++) { // 遍历背包
for (int i = 1; i < m + 1; i++) { // 遍历物品
if (v[i][0] > j) {
dp[i][j] = dp[i-1][j]; // 都不买
} else {
if (v[i][0] <= j) { // 只买主件
dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i][0]]+w[i][0]);
}
if (v[i][0]+v[i][1] <= j) { // 买主件+附件1
dp[i][j] = max(dp[i][j], dp[i-1][j-v[i][0]-v[i][1]]+w[i][0]+w[i][1]);
}
if (v[i][0]+v[i][2] <= j) { // 买主件+附件2
dp[i][j] = max(dp[i][j], dp[i-1][j-v[i][0]-v[i][2]]+w[i][0]+w[i][2]);
}
if (v[i][0]+v[i][1]+v[i][2] <= j) { // 买主件+附件1+附件2
dp[i][j] = max(dp[i][j], dp[i-1][j-v[i][0]-v[i][1]-v[i][2]]+w[i][0]+w[i][1]+w[i][2]);
}
}
}
}
cout << dp[m][N] * 10 << endl;
return 0;
}
参考文献
【华为机试】HJ16 购物单