购物单这道题看上去就是用01背包去解的,但是题中加入了附件,感觉加了点干扰因素,如果笔试第一次遇到,肯定做不出来。
看了一些题解,都是按照主件分组。因为题目要求就是"每个主件可以有 0 个、 1 个或 2 个附件",所以每个主件最多2个附件,然后可以按照附件个数分为主件、主件+附件1、主件+附件2、主件+附件1+附件2这几种情况,在考虑每个主件时,把这4种情况都算一遍,取最大值即可。
题解的代码都能搜到,先把过程图示写一下,最重要的是把过程搞清楚,然后再写代码就容易了,不要试图去记忆别人的代码。
题目给的例子有2个,第1个例子,带有附件的主件是排在第1位的,这样在只考虑第1个主件的所有情况时,不会用到前面的主件结果,因为前面没有主件,而第2、第3个主件没有附件,就和经典的01背包是一样的了。所以这个例子体现不出差别。所以选第2个例子,带有附件的主件不在第1位。
参考这个回答,购物单_牛客题霸_牛客网,"由于价格都是10的整数倍,我们统一在数据处理的时候都缩小十倍处理",就按这样做。
这里处理的时候,把主件下标从0开始。
dp表格:


第0行只有1个主件,不用列举了。
第1行:


第2行:


这个过程中把主件和附件做了1个对应关系,所以对于原始输入还要做下转换。按照这个过程写的代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in))) {
String s = bufferedReader.readLine();
String[] array = s.split(" ");
//n: 总钱数
int n = Integer.parseInt(array[0]);
//总钱数作除以10处理
n /= 10;
//m: 可购买物品个数
int m = Integer.parseInt(array[1]);
List<Goods> mains = new ArrayList<>();
//由于要做一个主物品和其附件物品列表的对应关系,而且主物品要单独抽出来用,所以主物品在旧列表和新列表下标是有对应关系的
//主物品下标变换
Map<Integer, Integer> mainIndexMap = new HashMap<>();
//map: 主物品原始下标----对应附件物品list
Map<Integer, List<Goods>> tMainSecondariesMap = new HashMap<>();
//map: 主物品新下标----对应附件物品list
Map<Integer, List<Goods>> mainSecondariesMap = new HashMap<>();
//主件前面的附件个数,用以记录主物品下标变换
int tCount = 0;
for (int i = 0; i < m; i++) {
s = bufferedReader.readLine();
array = s.split(" ");
int price = Integer.parseInt(array[0]);
//价格作除以10处理
price /= 10;
int importance = Integer.parseInt(array[1]);
int type = Integer.parseInt(array[2]);
Goods goods = new Goods(price, importance);
//主物品
if (type == 0) {
//原始列表把附件删掉,主件保持原来的顺序,所以主件的下标就要减去tCount
mainIndexMap.put(i, i - tCount);
mains.add(goods);
}
//附件物品
else {
tCount++;
//有附件了,更新tMainSecondariesMap
//原始输入的下标是从1开始的,附件中记录的主件下标也是从1开始的,这里减去1
List<Goods> list = tMainSecondariesMap.get(type - 1);
if (list == null) {
list = new ArrayList<>();
tMainSecondariesMap.put(type - 1, list);
}
list.add(goods);
}
}
for (Map.Entry<Integer, List<Goods>> entry : tMainSecondariesMap.entrySet()) {
Integer originalIndex = entry.getKey();
List<Goods> list = entry.getValue();
Integer index = mainIndexMap.get(originalIndex);
mainSecondariesMap.put(index, list);
}
// for (Goods main : mains) {
// System.out.println(main);
// }
// System.out.println("################");
// System.out.println(tMainSecondariesMap);
// System.out.println("################");
// System.out.println(mainSecondariesMap);
int tMaxSatisfaction = maxSatisfaction(n, mains, mainSecondariesMap);
//钱数、价格前面做了除以10处理,这里结果要乘以10
int maxSatisfaction = tMaxSatisfaction * 10;
System.out.println(maxSatisfaction);
}
}
/**
* @param n 总钱数,由于都是10的整数倍,这里作除以10的处理,后面结果再乘以10即可,效果是一样的
* @param mains 主物品list
* @param mainSecondariesMap map: 主物品下标----对应附件物品list
* @return
*/
private static int maxSatisfaction(int n, List<Goods> mains, Map<Integer, List<Goods>> mainSecondariesMap) {
//状态数组dp[i][j]:只能选0~i主物品时,当钱数为j时的最大满意度
//钱数是n,要取到n,长度是n+1
int[][] dp = new int[mains.size()][n + 1];
dp[0][0] = 0;
for (int i = 0; i < mains.size(); i++) {
Goods main = mains.get(i);
List<Goods> secondaries = mainSecondariesMap.get(i);
Goods s0 = null;
Goods s1 = null;
if (secondaries != null && !secondaries.isEmpty()) {
s0 = secondaries.get(0);
if (secondaries.size() == 2) {
s1 = secondaries.get(1);
}
}
//钱数是n,n是可以取到的
for (int j = 0; j <= n; j++) {
if (j == 0) {
dp[i][j] = 0;
continue;
}
//如果钱数小于主物品价格,则无法选择此主物品
if (j < main.price) {
//如果前面没有商品,则选不了任何商品,满意度为0
if (i == 0) {
dp[i][j] = 0;
}
//前面有商品,则选上一个商品位置,此钱数的最大满意度
else {
dp[i][j] = dp[i - 1][j];
}
continue;
}
//钱数大于等于主物品价格,求出选主物品、主物品+附件1、主物品+附件2、主物品+附件1+附件2所有情况的满意度,取最大的哪一个
//前面没有主物品
if (i == 0) {
int maxSatisfaction = 0;
//选主物品时的满意度
int satisfaction = main.price * main.importance;
if (satisfaction > maxSatisfaction) {
maxSatisfaction = satisfaction;
}
//选主物品+附件1的满意度
if (s0 != null && j - main.price - s0.price >= 0) {
satisfaction = main.price * main.importance + s0.price * s0.importance;
if (satisfaction > maxSatisfaction) {
maxSatisfaction = satisfaction;
}
}
//选主物品+附件2的满意度
if (s1 != null && j - main.price - s1.price >= 0) {
satisfaction = main.price * main.importance + s1.price * s1.importance;
if (satisfaction > maxSatisfaction) {
maxSatisfaction = satisfaction;
}
}
//选主物品+附件1+附件2的满意度
if (s0 != null && s1 != null && j - main.price - s0.price - s1.price >= 0) {
satisfaction = main.price * main.importance + s0.price * s0.importance + s1.price * s1.importance;
if (satisfaction > maxSatisfaction) {
maxSatisfaction = satisfaction;
}
}
//此位置,此钱数选此物品的4种情况的最大满意度,和上一个商品位置,此钱数的最大满意度,取较大的
dp[i][j] = maxSatisfaction;
}
//前面有主物品
else {
int maxSatisfaction = 0;
//选主物品时的满意度
int satisfaction = main.price * main.importance + dp[i - 1][j - main.price];
if (satisfaction > maxSatisfaction) {
maxSatisfaction = satisfaction;
}
//选主物品+附件1的满意度
if (s0 != null && j - main.price - s0.price >= 0) {
satisfaction = main.price * main.importance + s0.price * s0.importance
+ dp[i - 1][j - main.price - s0.price];
if (satisfaction > maxSatisfaction) {
maxSatisfaction = satisfaction;
}
}
//选主物品+附件2的满意度
if (s1 != null && j - main.price - s1.price >= 0) {
satisfaction = main.price * main.importance + s1.price * s1.importance
+ dp[i - 1][j - main.price - s1.price];
if (satisfaction > maxSatisfaction) {
maxSatisfaction = satisfaction;
}
}
//选主物品+附件1+附件2的满意度
if (s0 != null && s1 != null && j - main.price - s0.price - s1.price >= 0) {
satisfaction = main.price * main.importance + s0.price * s0.importance + s1.price * s1.importance
+ dp[i - 1][j - main.price - s0.price - s1.price];
if (satisfaction > maxSatisfaction) {
maxSatisfaction = satisfaction;
}
}
//此位置,此钱数选此物品的4种情况的最大满意度,和上一个商品位置,此钱数的最大满意度,取较大的
dp[i][j] = Math.max(maxSatisfaction, dp[i - 1][j]);
}
}
}
// for (int i = 0; i < dp.length; i++) {
// System.out.println(Arrays.toString(dp[i]));
// }
//dp[mains.length - 1][n]:mains所有物品都可选时,且钱数取到n时,最大满意度,即所有情况最大满意度
return dp[mains.size() - 1][n];
}
/**
* 商品
*/
private static class Goods {
//价格
protected int price;
//重要度
protected int importance;
public Goods() {
}
public Goods(int price, int importance) {
this.price = price;
this.importance = importance;
}
@Override
public String toString() {
return "Goods{" +
"price=" + price +
", importance=" + importance +
'}';
}
}
}