购物单
描述
王强决定把年终奖用于购物,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
主件 | 附件 |
电脑 | 打印机,扫描仪 |
书柜 | 图书 |
书桌 | 台灯,文具 |
工作椅 | 无 |
如果要买归类为附件的物品,必须先买该附件所属的主件,且每件物品只能购买一次。
每个主件可以有 0 个、 1 个或 2 个附件。附件不再有从属于自己的附件。
王强查到了每件物品的价格(都是 10 元的整数倍),而他只有 N 元的预算。除此之外,他给每件物品规定了一个重要度,用整数 1 ~ 5 表示。他希望在花费不超过 N 元的前提下,使自己的满意度达到最大。
满意度是指所购买的每件物品的价格与重要度的乘积的总和,假设设第i件物品的价格为v[i],重要度为w[i],共选中了k件物品,编号依次为1,2,...,j1,j2,...,jk,则满意度为:v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk]。(其中 * 为乘号)
请你帮助王强计算可获得的最大的满意度。
- 输入描述:
输入的第 1 行,为两个正整数N,m,用一个空格隔开:
(其中 N ( N<32000 )表示总钱数, m (m <60 )为可购买的物品的个数。)
从第 2 行到第 m+1 行,第 j 行给出了编号为 j-1 的物品的基本数据,每行有 3 个非负整数 v p q
(其中 v 表示该物品的价格( v<10000 ), p 表示该物品的重要度( 1 ~ 5 ), q 表示该物品是主件还是附件。如果 q=0 ,表示该物品为主件,如果 q>0 ,表示该物品为附件, q 是所属主件的编号)
- 输出描述:
输出一个正整数,为张强可以获得的最大的满意度。
自我检讨
写完这题感觉自己是个“大聪明”。甚至还气的发了个报错反馈。。这波写了注释都可以敲错代码,更新函数写了个i-1然后死活过不了。真是尬死了。
然后,到了我们的题解环节
首先根据题目可以发现这是一个经过改编的01背包问题,做完这道题相信小伙伴们也和纳西妲一样能够对动态规划有了更深入的理解(应用算法模板)。
由于在选物品时有区分主件、附件,因此在物品大小(价格)和物品价值(重要性)上我选择用二维数组存放,将附件绑定到主件所在的行上,这样一来无论是写代码时还是思考问题时都显得比较直观。
输入
为了让我们的算法看起来比较直观,在输入时应当做好处理,建立大小为(m+1,3)的数组,m+1行是为了将下标与物品序号对齐,3列则是一个主件我们根据题目发现最多也就2个附件(1+2)。为了让整体占用空间较小将价格方面的数据统一缩小10倍,这样以来在动态规划算法进行时会比较快。(循环次数缩小10倍!)
动态规划
相较于传统01背包问题,这道题需要多加几个特殊条件:
- 只取主件而不取其附件
- 取主件和该主件对应的附件1
- 取主件和该主件对应的附件2
- 取出一套
下面直接上代码吧(反正写的时候就把思路写在代码里了。。所以感觉直接看代码也没啥问题)
#include <vector>
#include <iostream>
using namespace std;
int main() { //解法的前提是每件物品只能买一次,否则会出现数据遗漏覆盖
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(0);
int N, m;
cin >> N >> m;
N /= 10; // 王强查到了每件物品的价格(都是 10 元的整数倍)
// price[主件][附件]
vector<vector<int>> price(m + 1, vector<int>(3,
0)); // 构建商品(及其附件)价格数组,并初始化为0
vector<vector<int>> value(m + 1, vector<int>(3,
0)); // 构建商品(及其附件)重要度数组,初始化为0
for (int i = 1; i <= m; i++) {
int v, p, q;
cin >> v >> p >> q;
// 当该物品为主件时
if (q == 0) {
price[i][0] = v / 10;// 价格同步缩小十倍
value[i][0] = p;
}
// 当物品为附件时
else {
// 主件q是否已经有一个附件
// 如果有,则该输入为主件q的第二个附件的值
if (price[q][1] != 0) {
price[q][2] = v / 10;
value[q][2] = p;
} else { //没有则为主件q的第一个附件
price[q][1] = v / 10;
value[q][1] = p;
}
}
}
/*
-------------------------动态规划--------------------------------------------------
*/
vector<vector<int>> dp(m + 1, vector<int>(N + 1, 0));
for (int i = 1; i <= m; i++) {//取件数
for (int j = 1; j <= N; j++) {//背包大小(金额预算)
int p = price[i][0], v = value[i][0];
int p1 = price[i][1], v1 = value[i][1];
int p2 = price[i][2], v2 = value[i][2];
// 分四种情况讨论:(在第一轮比较中就与历史值进行比较,因此后续更新状态只需和当前状态dp[i][j]比较即可)
// 1、只买主件,只有买了主件才有后续买附件(更新参数:dp[i-1][j-p]+p*v)
if (j >= p) {//预算足够时
dp[i][j] = max(dp[i - 1][j - p] + p * v, dp[i - 1][j]);
} else dp[i][j] = dp[i - 1][j];
// 2、买主件和其附件1(更新参数:dp[i-1][j-p-p1]+p*v+p1*v1)
if (j >= p + p1) {
dp[i][j] = max(dp[i - 1][j - p - p1] + p * v + p1 * v1, dp[i][j]);
} else dp[i][j] = dp[i][j];
// 3、买主件和其附件2(更新参数:dp[i-1][j-p-p2]+p*v+p2*v2)
if (j >= p + p2) {
dp[i][j] = max(dp[i - 1][j - p - p2] + p * v + p2 * v2, dp[i][j]);
} else dp[i][j] = dp[i][j];
// 4、买全套(更新参数:dp[i-1][j-p-p1-p2]+p*v+p1*v1+p2*v2)
if (j >= p + p1 + p2) {
dp[i][j] = max(dp[i - 1][j - p - p1 - p2] + p * v + p1 * v1 + p2 * v2,
dp[i][j]);
} else dp[i][j] = dp[i][j];
}
}
cout << dp[m][N] * 10 << endl;
return 0;
}