牛客网-华为机试练习题 16 购物单
题目描述
王强今天很开心,公司发给N元的年终奖。王强决定把年终奖用于购物,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
| 主件 | 附件 |
| ------ | -------------- |
| 电脑 | 打印机,扫描仪 |
| 书柜 | 图书 |
| 书桌 | 台灯,文具 |
| 工作椅 | 无 |
如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有 0 个、 1 个或 2 个附件。附件不再有从属于自己的附件。王强想买的东西很多,为了不超出预算,他把每件物品规定了一个重要度,分为 5 等:用整数 1 ~ 5 表示,第 5 等最重要。他还从因特网上查到了每件物品的价格(都是 10 元的整数倍)。他希望在不超过 N 元(可以等于 N 元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第j件物品的价格为v[j],重要度为w[j],共选中了 k 件物品,编号依次为j1,j2,……,jk,则所求的总和为:
v[j1]*w[j1]+v[j2]*w[j2]+…+v[jk]*w[jk] 。(其中 * 为乘号)
请你帮助王强设计一个满足要求的购物单。
输入描述:
输入的第 1 行,为两个正整数,用一个空格隔开:N m
(其中 N ( <32000 )表示总钱数, m ( <60 )为希望购买物品的个数。)
从第 2 行到第 m+1 行,第 j 行给出了编号为 j-1 的物品的基本数据,每行有 3 个非负整数 v p q
(其中 v 表示该物品的价格( v<10000 ), p 表示该物品的重要度( 1 ~ 5 ), q 表示该物品是主件还是附件。如果 q=0 ,表示该物品为主件,如果 q>0 ,表示该物品为附件, q 是所属主件的编号)
输出描述:
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值( <200000 )。
示例1
输入
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
输出
2200
思路:
本质问题是一个背包问题,那么就采用动态规划的方法来解决,这里省去对动态规划问题解决背包问题的介绍(具体介绍见背包问题总结一文),这里直接参照动态规划方法给出思路。
首先是审题,
一共1000元,最多可以购买5个物品,一共给出来了5个物品,所以,物品个数不是限制,1000元才是
然后给出来物品的信息如下:
价格 | 重要度 | 主件/附件 | 编号 |
---|---|---|---|
800 | 2 | 0 | A |
400 | 5 | 1 | B |
300 | 5 | 1 | C |
400 | 3 | 0 | D |
500 | 2 | 0 | E |
也就是说,A,D,E是主件,B,C是附件,买附件必须买主件,要求,所选物品的价格与重要度乘积之和最大。
对应0-1背包问题如下
比较项目 | 该问题 | 0-1背包问题 |
---|---|---|
约束 | 花费(也就是钱) | 背包容量 |
求解 | 价格与重要度乘积之和最大 | 价值最大 |
额外约束 | 买附件必须买主件 | 无 |
可以先进行的处理是
- 将每个物品的价格与重要度相乘,作为价值向量v
- 计算购买某个物品时,需要额外花的钱ex_w,和额外产生的价值ex_v
- 将每个物品的价格和购买该物品时需要额外话的钱相加作为该物品的w的值,该物品产生的价值v和额外产生的价值ex_v相加作为该物品的v
之后,就可以按求解0-1问题的基本步骤去做了
- 建立矩阵dp,矩阵有5行(一共5个物品),有1000+1列(一共1000元)
- 先填第一行,如果钱j(背包容量)小于产生的价值v[i](物品的价值),那么填0,否则,填v[i]
- 再填其余行
- 如果j小于v[i],填dp[i-1,j],
- 如果j大于等于v[i],填max( dp[i-1,j] ,dp[i-1,j-w[i]]+v[i])
解决代码:
import java.util.Scanner;
//加了限制条件的背包问题
public class Main {
public static int getMaxValue(int[] val, int[] weight, int[] q, int n, int w) {
int[][] dp = new int[n + 1][w + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= w; j++) {
if (q[i-1] == 0) { // 主件
if (weight[i - 1] <= j) // 用j这么多钱去买 i 件商品 可以获得最大价值
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j- weight[i - 1]]+ val[i - 1]);
}
else { //附件
if (weight[i - 1] + weight[q[i - 1]] <= j) //附件的话 加上主件一起算
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j- weight[i - 1]]+ val[i - 1]);
}
}
}
return dp[n][w];
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
while (input.hasNextInt()) {
int n = input.nextInt(); // 总钱数
int m = input.nextInt(); // 商品个数
int[] p = new int[m];
int[] v = new int[m];
int[] q = new int[m];
for (int i = 0; i < m; i++) {
p[i] = input.nextInt(); // 价格
v[i] = input.nextInt() * p[i]; // 价值
q[i] = input.nextInt(); // 主or附件
}
System.out.println(getMaxValue(v, p, q, m, n));
}
}
}
总结:
- 背包问题要好好看一下
- 可以在本子上写下变量的含义