购物单 与 01背包

牛客网试题地址:
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 )。

问题理解:

  1. 总容量: N, 物品数量:m, 物品价值: 价格*重要等级(等级越大越高), 求总容量范围内的最大价值.
  2. 物品只能放入一次, 所以这是一个01背包问题.
  3. 需要注意的是物品还有主从属性, 一个主件可以带0-2个附件, 而且有附件的时候必须是包含主件的.
    由主件和附件的关系, 我们可以只考虑 主件, 主件+1个附件(附件1或者附件2), 主件+两个附件的关系.

解题思路:

存放结果的数组我们使用一个int[][] dp的二维数组
初始值 int[i][0] 和int[0][j] 均为0
根据01背包的状态转义方程考虑:
当前物品是主键的情况下:

  1. 如果放入的第i件物品(或者第i件物品+1/2附件)的价格超过当前价格j, 则dp[i][j]=dp[i-1][j];
  2. 否则, 取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]);
        }

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值