牛客-购物单(01背包问题)过程梳理、代码

文章介绍了一道购物单问题的解法,运用01背包原理,结合主件和附件的特性,通过动态规划求得最大满意度。
摘要由CSDN通过智能技术生成

购物单_牛客题霸_牛客网

购物单这道题看上去就是用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 +
				'}';
		}
	}
	
}

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值