一、 题目描述
新学期伊始,适逢顿顿书城有购书满 x 元包邮的活动,小 P 同学欣然前往准备买些参考书。
一番浏览后,小 P 初步筛选出 n 本书加入购物车中,其中第 i 本(i≤n)的价格为 ai 元。
考虑到预算有限,在最终付款前小 P 决定再从购物车中删去几本书(也可以不删),使得剩余图书的价格总和 m 在满足包邮条件(m≥x)的前提下最小。
试帮助小 P 计算,最终选购哪些书可以在凑够 x 元包邮的前提下花费最小?
输入格式
从标准输入读入数据。
输入的第一行包含空格分隔的两个正整数 n 和 x,分别表示购物车中图书数量和包邮条件。
接下来输入 n 行,其中第 i 行(1≤i≤n)仅包含一个正整数 ai,表示购物车中第 i 本书的价格。输入数据保证 n 本书的价格总和不小于 x。
输出格式
输出到标准输出。
仅输出一个正整数,表示在满足包邮条件下的最小花费。
样例1输入
4 100
20
90
60
60
Data
样例1输出
110
Data
样例1解释
购买前两本书(20+90)即可包邮且花费最小。
样例2输入
3 30
15
40
30
Data
样例2输出
30
Data
样例2解释
仅购买第三本书恰好可以满足包邮条件。
样例3输入
2 90
50
50
Data
样例3输出
100
Data
样例3解释
必须全部购买才能包邮。
子任务
70% 的测试数据满足:n≤15;
全部的测试数据满足:n≤30,每本书的价格 ai≤10^4 且 x≤a1+a2+⋯+an。
提示
对于 70% 的测试数据,直接枚举所有可能的情况即可。
二、题解
分析
该问题其实是动态规划01背包问题的变种,题目满足x≤a1+a2+⋯+an, 我们需要挑选一些组合,使得a1+a2+⋯+an 最接近 x ,同时又要比x大 。这并不符合01背包问题的一般情况,只是很类似,需要进一步转换。
考虑样例1 ,
4 100
20
90
60
60
我们先求出数组之和 20+90+60+60=230代表当前购物车的总价值,我们需要挑选一些还回去,挑选的总数不得超过 230-100 = 130, 130就是背包容量,待挑选的物品就是购物车中的所有物品, 且此时物品的重量和价值都是同一个数组。价值就是重量,重量就是价值,假设挑选出的待放回的物品的总价值为120, 那么最终的包邮的最低价就是230 - 120 = 110。
01背包问题
什么是背包问题, 说再多不如一个栗子
现在有一个背包,容量为20(最大能承受的重量),还有很多物品等待装入背包,物品信息如下:
| 物品编号 i | 1 | 2 | 3 | 4 |
|---|---|---|---|---|
| 物品价值 V[i] | 20 | 10 | 15 | 13 |
| 物品重量 H[i] | 7 | 2 | 8 | 7 |
问: 不超过背包容量的前提下,如何选择物品组合可以让价值最大化。最大的价值是多少?
符合这中基本的结构我们就可以使用动态规划的思想来写。
- 初始化一个二维数组
dp[4+1][20+1]。 - dp[i][j]的含义:考虑背包容量为
j, 且只考虑前i件物品时的最优解。 - 第一行和第一列初始化为0 ,这个其实就是极限情况,如果背包容量0,不管物品价值再高,最优解都是0。同理,如果没有物品,背包容量再大,最优解也是0。
- 填写dp数组,需要考虑是否装得下。
- 考虑dp[i][j]代表最优解, i代表前i件物品,j代表背包容量。对当前物品来说,无非就是
装进背包,或者不装进背包, 然后取二者的较大值即可。同时需要考虑能否装下的边界条件。也就是:- 如果装不下 临界条件是j<H[i](其实是j<H[i-1],因为初始的数组长度加一了 ), 就只能选择不装,此时
dp[i][j] = dp[i-1][j],因为没装下当前物品,因此背包容量不用预留,考虑前i件物品和i-1件物品是一样的 - 否则(能装下),就需要考虑装还是不装,取他们的最大值即可。Math.max(装下的最优解,不装的最优解)。
- 不装:
dp[i][j] = dp[i-1][j]上面已经解释过,不再解释。 - 装:
dp[i][j] = dp[i-1][j-H[i-1]]+V[i-1]。 解释: 装下这件物品,留给前i-1 件物品的空间只剩下j-V[i-1], 包的在这种情况的最优解为考虑前i-1 件物品,背包容量为j-V[i-1]的情况加上V[i-1]
- 不装:
- 如果装不下 临界条件是j<H[i](其实是j<H[i-1],因为初始的数组长度加一了 ), 就只能选择不装,此时
- 最优解就是
dp[4][20]
三、 代码
java 版本,(CSP考试不支持使用javaScript, 不过不影响,殊途同归!!!!)
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int x = sc.nextInt();
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = sc.nextInt();
}
int sumArr = Arrays.stream(arr).sum();
int sum = sumArr - x;
int[][] dp = new int[n + 1][sum + 1];
for (int i = 1; i < n + 1; i++) {
for (int j = 1; j < sum + 1; j++) {
// 装不下 不装
if (j < arr[i - 1]) {
dp[i][j] = dp[i - 1][j];
} else {
// 能装下
dp[i][j] = Math.max(dp[i - 1][j - arr[i - 1]] + arr[i - 1], dp[i - 1][j]);
}
}
}
System.out.println(sumArr - dp[n][sum]);
}
}