何以包邮

169 阅读5分钟

一、 题目描述

新学期伊始,适逢顿顿书城有购书满 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 = 130130就是背包容量待挑选的物品就是购物车中的所有物品, 且此时物品的重量和价值都是同一个数组。价值就是重量,重量就是价值,假设挑选出的待放回的物品的总价值为120, 那么最终的包邮的最低价就是230 - 120 = 110

01背包问题

什么是背包问题, 说再多不如一个栗子

image.png

现在有一个背包,容量为20(最大能承受的重量),还有很多物品等待装入背包,物品信息如下:

物品编号 i1234
物品价值 V[i]20101513
物品重量 H[i]7287

问: 不超过背包容量的前提下,如何选择物品组合可以让价值最大化。最大的价值是多少?

符合这中基本的结构我们就可以使用动态规划的思想来写。

  1. 初始化一个二维数组dp[4+1][20+1]
  2. dp[i][j]的含义:考虑背包容量为j, 且只考虑前i件物品时的最优解。
  3. 第一行和第一列初始化为0 ,这个其实就是极限情况,如果背包容量0,不管物品价值再高,最优解都是0。同理,如果没有物品,背包容量再大,最优解也是0。
  4. 填写dp数组,需要考虑是否装得下。
  5. 考虑dp[i][j]代表最优解, i代表前i件物品,j代表背包容量。对当前物品来说,无非就是装进背包,或者不装进背包, 然后取二者的较大值即可。同时需要考虑能否装下的边界条件。也就是:
    1. 如果装不下 临界条件是j<H[i](其实是j<H[i-1],因为初始的数组长度加一了 ), 就只能选择不装,此时dp[i][j] = dp[i-1][j] ,因为没装下当前物品,因此背包容量不用预留,考虑前i件物品和i-1件物品是一样的
    2. 否则(能装下),就需要考虑装还是不装,取他们的最大值即可。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]
  6. 最优解就是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]);
        
    }
}