牛客网试题地址:
https://www.nowcoder.com/practice/f9c6f980eeec43ef85be20755ddbeaf4?tpId=37&&tqId=21239&rp=1&ru=/ta/huawei&qru=/ta/huawei/question-ranking
输入描述:
输入的第 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 )。
问题理解:
- 总容量: N, 物品数量:m, 物品价值: 价格*重要等级(等级越大越高), 求总容量范围内的最大价值.
- 物品只能放入一次, 所以这是一个01背包问题.
- 需要注意的是物品还有主从属性, 一个主件可以带0-2个附件, 而且有附件的时候必须是包含主件的.
由主件和附件的关系, 我们可以只考虑 主件, 主件+1个附件(附件1或者附件2), 主件+两个附件的关系.
解题思路:
存放结果的数组我们使用一个int[][] dp的二维数组
初始值 int[i][0] 和int[0][j] 均为0
根据01背包的状态转义方程考虑:
当前物品是主键的情况下:
- 如果放入的第i件物品(或者第i件物品+1/2附件)的价格超过当前价格j, 则dp[i][j]=dp[i-1][j];
- 否则, 取dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-主件和其附件的价格之和]+主件和其附件的价值之和);
当前物品是附件: 则跳过(当做不放入处理), 采用dp[i][j] = dp[i-1][j]为其赋值;
代码:
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
int N = sc.nextInt();
int m = sc.nextInt();
//存放价格和重要级别的数组采用二位数组的方式, 第一列存放主件, 如果有附件则存放在第二和第三列
int[][] v = new int[m+1][3];
int[][] p = new int[m+1][3];
int[] q = new int[m+1];
for(int i = 1; i<m+1; i++){
v[i][0] = sc.nextInt();
p[i][0] = sc.nextInt();
q[i] = sc.nextInt();
// 如果这是一个子元素, 则对其父元素赋值
if(q[i] > 0){
// 如果父元素已经有一个子元素了, 则将值放入第二个子元素
if(v[q[i]][1] == 0){
v[q[i]][1] = v[i][0];
p[q[i]][1] = p[i][0];
}else if(v[q[i]][1]>0){
v[q[i]][2] = v[i][0];
p[q[i]][2] = p[i][0];
}
}
}
int[][] dp = new int[m+1][N+1];
for(int i = 1; i<m+1; i++){
int tmpVal0 = v[i][0]* p[i][0];
int tmpVal1 = 0, tmpVal2 =0, tmpVal012 = 0, tmpVal01=0, tmpVal02=0;
int v01=0, v02 =0, v012=0;
// 主件+附件1的价值tmpVal01 以及价格v01
if(v[i][1] != 0){
tmpVal1 = v[i][1]* p[i][1];
tmpVal01 = tmpVal0 + tmpVal1;
v01 = v[i][0]+v[i][1];
}
// 主件+附件2的价值tmpVal02 以及价格v02
if(v[i][2] != 0){
tmpVal2 = v[i][2]* p[i][2];
tmpVal02 = tmpVal0 + tmpVal2;
v02 = v[i][0]+v[i][2];
}
// 主件+附件1+附件2的价值tmpVal012 以及价格v012
if(v[i][1] != 0 && v[i][2] != 0){
tmpVal012 = tmpVal0+tmpVal1+tmpVal2;
v012 = v[i][0]+v[i][1]+v[i][2];
}
for(int j = 1; j<N+1; j++){
// 如果是附件, 则跳过
if(q[i] > 0){
dp[i][j] = dp[i-1][j];
}else{
// 每一次判断容量后面都会有dp[i][j] = dp[i-1][j]. 所以提取出来了
dp[i][j] = dp[i-1][j];
if(v[i][0] > 0 && v[i][0]<= j){
dp[i][j] = Math.max(dp[i][j], dp[i-1][j-v[i][0]]+tmpVal0);
}
// Math.max不但要比较当前i和上一个i的区别, 也需要比较带不带附件之间的大小, 所以每次比较后放入dp[i][j], 下一次使用dp[i][j]而不是01背中的dp[i-1][j](也可以用, 但是这样就不是两个之间的取max了, 写起来反而麻烦)
if(v01 > 0 && v01 <= j){
dp[i][j] = Math.max(dp[i][j], dp[i-1][j-v01]+tmpVal01);
}
if(v02 > 0 && v02 <=j){
dp[i][j] = Math.max(dp[i][j], dp[i-1][j-v02]+tmpVal02);
}
if(v012 > 0 && v012 <= j){
dp[i][j] = Math.max(dp[i][j], dp[i-1][j-v012]+tmpVal012);
}
}
}
}
System.out.println(dp[m][N]);
}
}
}