最小剩余空间-第11届蓝桥杯省赛Python真题精选

发布时间:2024年01月17日

[导读]:超平老师的Scratch蓝桥杯真题解读系列在推出之后,受到了广大老师和家长的好评,非常感谢各位的认可和厚爱。作为回馈,超平老师计划推出《Python蓝桥杯真题解析100讲》,这是解读系列的第31讲。

最小剩余空间,本题是2020年6月20日举办的第11届蓝桥杯青少组Python编程省赛真题,题目要求编程计算空间问题,在n个物品中,任取若干个装入容器内,如何使容器的剩余空间为最小。

先来看看题目的要求吧。

一.题目说明

时间限制:4000Ms

内存限制:589824K3

编程实现

现有一个容器,其容量为V(0 < V < 1001,正整数),同时有n个物品(0< n ≤ 30),每个物品体积大小不同(正整数)。在n个物品中,任取若干个装入容器内,使容器的剩余空间为最小。

输入描述

输入容器大小V(0 < V < 1001,正整数)

输入物品数量n(0 < n ≤ 30)

输入n个物品的不同大小(正整数)

输出描述

剩余最小空间值

样例输入:

100

4

50

20

45

19

样例输出

5

样例输入说明:

"100"输入的是容器大小V,"4"输入的是物品数量n,"50", "20","45","19"输入的是4个物品体积。

样例输出说明

“5”是容器大小100减掉4个物体不同组合最为接近容器大小的一组值。(物品组合个数不限制,只找组合后最接近容器大小的值)

评分标准:

  • 20分:能正确输出一组数据;

  • 20分:能正确输出两组数据;

  • 20分:能正确输出三组数据;

  • 20分:能正确输出四组数据;

  • 20分:能正确输出五组数据。

二.思路分析

这是一道算法题,考查的知识点涉及枚举、组合算法和动态规划算法。

经典的装箱问题,这是信奥一本通书中的一道原题,题号是1295。对于C++而言,解决的方法是动态规划,对于Python而言,除了动态规划,还可以使用组合算法来实现。

首先,我们要彻底理解题目的意思,在n个物品中,任取若干个装入容器内,使容器的剩余空间为最小。容器的容量是固定的,剩余空间最小,就意味着物品的组合要占用最大的空间。

需要注意,每个物品最多只能取1次,要么不取,要么就取,就只有两种不同的状态。

如果将物品的体积当作价值,问题就变成了在固定大小的容器中装入价值最大的物品,这不就是鼎鼎大名的01背包吗?

图片

接下来,我们分别使用组合和动态规划两种算法来分析其实现思路。

1.组合算法

组合算法相对比较容易理解,就是找出所有的组合,看看在这些组合中,满足总体积小于V的组合中,哪个的总体积最大。

以题目给出的样例数据进行分析,容器的容量为100,4个物品的体积大小分别为50、20、45和19。

由于物品可以是任意组合的,我们分4种情况来讨论。

1). 只选取一个物品时,有4种组合,如下:

(50)、(20)、(45)、(19);

其中,总体积最大的是50,即只选第一个物品。

2). 选取两个物品时,有6种组合,如下:

(50,20)、(50,45)、(50,19)、(20,45)、(20,19)、(45,19)

其中,总体积最大的是95,即选择1和3两个物品。

3). 选取3个物品时,有4种组合,如下:

(50,20,45)、(50,20,19)、(50,45,19)、(20,45,19)

其中,组合(50, 20, 45)和(50, 45, 19)的总体积超过100,直接忽略,在剩下的两组中,总体积最大的是89,即选择1、2、4这3个物品。

4). 选取4个商品,只有一种组合,如下:

(50,20,45,19);

组合的总体积超过100,可以忽略。

由此,我们可以得出,在不超过100的组合中,选择50和45这两个物品的总容量是最大的,总体积是95,因此剩余空间是100?- 95 = 5。

对于组合的实现,我们可以自己编写代码来实现,但是Python已经提供了内置模块itertools库,可以高效地实现数据的排列组合。

关于itertools库的使用,在《排列组合-第11届蓝桥杯选拔赛Python真题精选》做过详细介绍,这里就不再赘述了。

2. 动态规划算法

动态规划的核心思想是将原问题拆解成若干个重叠的子问题,通过将子问题的解存储起来,避免重复计算,从而提高了算法的效率,其中的核心找到递推公式(状态转移方程)。

图片

对于动态规划,通常可以分成如下4个典型步骤:

  • 确定dp数组以及下标的含义

  • 确定递推公式

  • 初始化dp数组

  • 确定遍历顺序

对dp数组的确定最为关键,装箱问题需要考虑两个维度,分别是物品和体积,因此需要使用二维数组dp[i][j]来表示,其意思表示在大小为j的容器中,任取0~i中的物品组合时的最大体积

这里的i表示在前i个物品中任意组合,j则表示容器的大小,其取值有讲究,通常是每次增加1。

为了方便说明,我们换一组数据,假定总容量为10,有3个物品,其体积分别是5、8、4,可以制作表格如下:

图片

这是一张二维表格,行代表物品,列代表容器的容量,动态规划的过程其实就是如何填写这张表格的过程,填写过程中,始终牢记dp[i][j]的含义。

其中,物品0表示没有装入任何物品,体积0表示容器大小为0,在这两种情况下,结果都为0,所以都初始化为0。

最终,我们要计算出3种物品装入大小为10的容器中的最大体积,这就意味着i = 3,j = 10,对应于dp[3][10],也就是上面表格中最右下角的单元格。

这里的推导公式是什么呢?

此时,我们要使用拆解思维,将大问题拆分成小问题。再次牢记dp[i][j]的含义,它是指前i个物品装入大小为j的容器中的最大值。

不妨反问一下,对于第i个物品,我们到底要不要装入到大小为j的容器中呢,无非就是两种情况:

  • 不装

  • 装入

什么时候不装呢?当然是物品的大小超过当前容器的大小了。

比如将大小为5的物品装入容量为4的容器中,肯定装不下,此时dp[i][j] = dp[i -1][j],dp[i-1][j]的意思是指任意取前i-1个物品组合装入大小为j的容器中的最大体积。

dp[i][j]?=?dp[i-1][j]

从二维表格的角度来讲,dp[i-1][j]就是dp[i][j]正上方的单元格,这一下好理解了吧?

如果第i个物品的大小小于当前容器的大小,此时就要考虑一下,是装入划算呢,还是不装入划算呢?

如果要装入的话,那么首先就要将自己的体积累加起来,与此同时,它有可能会把其它物品挤出来,这就意味着前i-1个物品在容量只有(j - vi)的最大体积是dp[i-1][j-vi],其中vi是指第i个物品的体积。

是装入划算呢,还是不装入划算呢?

这是个问题,将两种情况拿出来较量一下,取较大的那一个就行,所以,其递推公式如下:

dp[i][j]?=?max(dp[i-1][j],?dp[i-1][j-vi]?+?vi)

注意,上面的vi应该是vi。

有了递推公式,就可以按照物品从上到下依次遍历,对于每个物品从左到右依次遍历,这是一个嵌套循环。

按照这个思路,可以将上面的表格填充完整,如下:

图片

强烈建议你按照上面的分析过程,一步一步地来填写这个表格,表格最右下角的单元格就是最大体积值。

思路有了,接下来,我们就进入具体的编程实现环节。

三.编程实现

根据上面的思路分析,我们使用两种方法来编写程序:

  • 组合算法;

  • 动态规划算法;

1. 组合算法

根据前面的思路分析,编写代码如下:

图片

代码不难,简单说明5点:

1). 在输入物品体积的时候,一行一个数据,因此需要使用循环逐个获取并存入列表;

2). combinations()函数有两个参数,第一个参数是可迭代对象,第二个参数是组合的个数,对于n个物品,可以选1个、2个、3个....n个,所以需要使用循环来处理;

3). combinations()函数返回的结果是可迭代对象,为了方便处理,这里使用了list()函数将其转成列表,并追加到res列表中;

4). 在组合过程中,并没有考虑超过v的组合,因此需要单独处理,这里使用了列表推导式,将过滤后的组合进行累加,并存入到列表res1中;

5). ?使用max()函数获取最大体积,然后使用v减去最大体积,就是剩余的最小空间了。

2. 动态规划算法

根据思路分析,我们编写代码如下:

图片

代码还是挺有难度的,简单说明两点:

1). 根据前面的思路分析,为方便计算,增加了物品0和体积0,因此二维数组的长度需要在给定值的基础上加1;

2). 构造dp数组的时候,使用了列表推导式;

实际上,对于二维数轴的实现,我们可以使用滚动数组的思想进行压缩和简化,然后通过一维数组来实现,其代码如下:

图片

关于滚动数组的原理和实现过程,这里就不再展开了,后续超平老师会开辟专题来讲述,代码理解起来有些难度,先参考一下吧。

接下来是测试环节了。

使用题目的样例数据,总容量为100,有4个物品,其体积分别是50、20、45、19,结果如下:

图片

假定总容量为10,有3个物品,其体积分别是5、8、4,结果如下:

图片

假定总容量为24,有6个物品,其体积分别是8、3、12、7、9、7,结果如下:

图片

至此,整个程序就全部完成了,你也可以输入不同的数字来测试效果。

四.总结与思考

本题的分数为100分,代码在15行左右,涉及到的知识点包括:

  • 循环语句,主要for...in循环;

  • 输入输出处理;

  • 系统函数的使用;

  • 列表运算;

  • 组合函数;

  • 动态规划算法;

作为高级组最后一题,难度较大,虽然代码不多,这里的难点是动态规划算法的理解和实现。

从题目本身的角度来讲,这是一个组合问题,通常可以使用枚举来实现,但是题目给出的v和n都是变化,使用枚举比较麻烦,而且枚举的效率不高,尤其是对于数据量较大的情况,考试时存在超时情况。

针对排列组合,在Python编程中,最简单的实现方式就是使用itertools库,重点是combinations()和permutations()两个函数。

在一般情况下,combinations函数的效率是很高的,因为它使用了高效的算法来生成组合,并且在实现上进行了优化。

然而,当元素数量较大或者要生成的组合数量非常庞大时,其效率会受到影响。这是因为生成所有可能的组合需要消耗大量的计算资源和内存空间。

在实际使用中,如果需要生成大量组合,可以考虑对算法进行优化,或者使用其他更高效的方法来处理。

作为经典算法,动态规划还是有些难度的,在学习的时候,一定要彻底理解dp数组的含义,结合二维表格来理解,最好的方法就是自己绘制二维表格,一步一步地填写好表格,关于动态规划,超平老师后面会有专题介绍,请持续关注。

超平老师给你留一道思考题,将二维数组简化为一维数组,其原理是什么,需要注意什么问题?

你还有什么好的想法和创意吗,也非常欢迎和超平老师分享探讨。

如果你觉得文章对你有帮助,别忘了点赞和转发,予人玫瑰,手有余香😄。

有需要源代码的,可以移步至“超平的编程课”gzh。

文章来源:https://blog.csdn.net/diamondwang2012/article/details/135651058
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。