题目描述:
王强决定把年终奖用于购物,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
主件 | 附件 |
电脑 | 打印机,扫描仪 |
书柜 | 图书 |
书桌 | 台灯,文具 |
工作椅 | 无 |
如果要买归类为附件的物品,必须先买该附件所属的主件,且每件物品只能购买一次。
每个主件可以有 0 个、 1 个或 2 个附件。附件不再有从属于自己的附件。
王强查到了每件物品的价格(都是 10 元的整数倍),而他只有 N 元的预算。除此之外,他给每件物品规定了一个重要度,用整数 1 ~ 5 表示。他希望在花费不超过 N 元的前提下,使自己的满意度达到最大。
满意度是指所购买的每件物品的价格与重要度的乘积的总和,假设设第i件物品的价格为v[i],重要度为w[i],共选中了k件物品,编号依次为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 是所属主件的编号)
输出描述:
输出一个正整数,为张强可以获得的最大的满意度。
示例1
输入:
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
输出:
2200
示例2
输入:
50 5
20 3 5
20 3 5
10 3 0
10 2 0
10 1 0
输出:
130
说明:
由第1行可知总钱数N为50以及希望购买的物品个数m为5;
第2和第3行的q为5,说明它们都是编号为5的物品的附件;
第4~6行的q都为0,说明它们都是主件,它们的编号依次为3~5;
所以物品的价格与重要度乘积的总和的最大值为10*1+20*3+20*3=130
解题思路:
该问题为经典的0-1背包问题的变形,如果有不懂0-1背包问题的网上有很多资料,这里主要是讲解0-1背包问题的变形解法。
首先,对于0-1背包问题的状态转移方程如下:
//j表示背包容量(本题中为持有的总金额),i表示当前遍历到的物品序号,
price[i], v[i]分别表示物品的价格和价值;
//1.放得下i号物品
if(j >= [prices[i]) {
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - price[i]] + v[i]);
}
//2.放不下
else dp[i][j] = dp[i-1][j];
而本题中较为麻烦的一点为:存在主件和附件,不能像0-1背包一样进行从头到尾的遍历,因为附件不能脱离主件而存在。题目中给出的信息是一个主件最多两个附件,因此我们的思路可以转化为只遍历主件,遍历到主件i时,如果i有附件,那么我们就考虑当前容量j是否能容纳下1.主件,2.主件+附件1,3.主键+附件2,4.主键+附件1+附件2.再将以上四种情况中可行的情况(背包容量够和当前主件i的附件的个数)和i不放入进行比较,选择最大的作为当前的dp[i][j]值。遍历到附件则跳过,将其dp[i][j] = dp[i - 1][j];
具体实现如下:本题中,每行输入三个数字,因此我们利用三个数组对每个物品的价格,满意度,是否附件进行保存:price[],val[],itemType[]。
int a, b, c;
vector<int> price;
vector<int> val;
vector<int> itemType;
for (int i = 0; i < m; ++i) {
cin >> a >> b >> c;
price.push_back(a / 10);
val.push_back(b);
itemType.push_back(c);
}
然后创造dp数组,很明显的可以知道对于不选择任何物品(i = 0)以及总金额为0(j = 0),其产生的效益值都为0;因此边界条件不用额外处理。
vector<vector<int>> dp(m + 1, vector<int>(1 + N, 0));
在遍历物品时,有一个小坑,就是我们录入price[],val[],itemType[]是从0开始的,而dp数组的第0行表示的是不选择任何物品,因此我们在遍历dp时,需要将i进行减一操作,才能刚好匹配上我们的price[],val[],itemType[]数组(当然你也可以事先将0这个位置进行填充,刚好就和dp数组的下标保持一致了)。
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= N; ++j) {
if (itemType[i - 1] == 0) {
vector<int> res;
vector<int> slaveIndex;
for (int k = 0; k < m; ++k) {
if (itemType[k] == i) {
slaveIndex.push_back(k);
}
}
其中itemType[i] == 0是验证当前物品是主件我们才对其进行操作:slaveIndex数组用来保存当前主件的物品i的附件所在的下标(附件1以及附件2的下标),res数组用来存储可能产生的值:1. i不加入 2. 只加入i;3.只加入i和他的第一个附件;4.只加入i和他的第二个附件;5.加入i和他的第一和第二个附件。
因此res数组中最多有五个值;我们的dp[i][j]也就是从这五个值中选择最大的一个。
int tempVal = dp[i - 1][j]; //1.不加入i,第一个值
res.push_back(tempVal);
if (j >= price[i - 1]) res.push_back(dp[i - 1][j - price[i - 1]] + price[i - 1] * val[i - 1]); //如果装得下i,只装入i,第二个值
//只有一个附件,装入这一个附件,第三个值。这种情况下res中只有三个值。
if (slaveIndex.size() == 1) {
if (price[slaveIndex[0]] + price[i - 1] <= j) {
res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[0]]] + price[i - 1] * val[i - 1]
+ price[slaveIndex[0]] * val[slaveIndex[0]]);
}
}
//两个附件,这种情况下五个值
if (slaveIndex.size() == 2) {
//只装附件1,第三个值
if (price[slaveIndex[0]] + price[i - 1] <= j) {
res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[0]]] + price[i - 1] * val[i - 1]
+ price[slaveIndex[0]] * val[slaveIndex[0]]);
}
//只装附件2,第四个值
if (price[slaveIndex[1]] + price[i - 1] <= j) {
res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[1]]] + price[i - 1] * val[i - 1]
+ price[slaveIndex[1]] * val[slaveIndex[1]]);
}
//都装入,第五个值
if (price[slaveIndex[0]] + price[slaveIndex[1]] + price[i - 1] <= j) {
res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[0]] -
price[slaveIndex[1]]] + price[i - 1] * val[i - 1] + price[slaveIndex[0]] *
val[slaveIndex[0]] + price[slaveIndex[1]] * val[slaveIndex[1]]);
}
}
/*cout<<i<<" "<<j<<": ";
for(auto it : res) {
cout<<it<<" ";
}*/
dp[i][j] = *(max_element(res.begin(), res.end())); //取最大的那个
注意每次试探装入时都有判断是否能否装得下(当前容量j是否够)。
最终完整代码如下:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
int N, m;
while (cin >> N >> m) { // 注意 while 处理多个 case
int a, b, c;
vector<int> price;
vector<int> val;
vector<int> itemType;
for (int i = 0; i < m; ++i) {
cin >> a >> b >> c;
price.push_back(a / 10);
val.push_back(b);
itemType.push_back(c);
}
//这里是题目中说了价格都是10的倍数,剪枝,降低时间复杂度。
N = N / 10;
vector<vector<int>> dp(m + 1, vector<int>(1 + N, 0));
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= N; ++j) {
if (itemType[i - 1] == 0) {
vector<int> res;
vector<int> slaveIndex;
for (int k = 0; k < m; ++k) {
if (itemType[k] == i) {
slaveIndex.push_back(k);
}
}
int tempVal = dp[i - 1][j];
res.push_back(tempVal);
if (j >= price[i - 1]) res.push_back(dp[i - 1][j - price[i - 1]] + price[i - 1] * val[i - 1]);
if (slaveIndex.size() == 1) {
if (price[slaveIndex[0]] + price[i - 1] <= j) {
res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[0]]] + price[i - 1] * val[i - 1]
+ price[slaveIndex[0]] * val[slaveIndex[0]]);
}
}
if (slaveIndex.size() == 2) {
if (price[slaveIndex[0]] + price[i - 1] <= j) {
res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[0]]] + price[i - 1] * val[i - 1]
+ price[slaveIndex[0]] * val[slaveIndex[0]]);
}
if (price[slaveIndex[1]] + price[i - 1] <= j) {
res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[1]]] + price[i - 1] * val[i - 1]
+ price[slaveIndex[1]] * val[slaveIndex[1]]);
}
if (price[slaveIndex[0]] + price[slaveIndex[1]] + price[i - 1] <= j) {
res.push_back(dp[i - 1][j - price[i - 1] - price[slaveIndex[0]] -
price[slaveIndex[1]]] + price[i - 1] * val[i - 1] + price[slaveIndex[0]] *
val[slaveIndex[0]] + price[slaveIndex[1]] * val[slaveIndex[1]]);
}
}
/*cout<<i<<" "<<j<<": ";
for(auto it : res) {
cout<<it<<" ";
}*/
dp[i][j] = *(max_element(res.begin(), res.end()));
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
/* for (int i = 0; i <= m; ++i) {
for (int j = 0; j <= N; ++j) {
cout << dp[i][j] << " ";
}
cout << endl;
}*/
cout << 10 * dp[m][N] << endl; //输出值,*10是为了还原剪枝操作
}
}
成功通过!