文章目录

  • 前言
  • 题目
  • 解题思路
    • 1.LeetCode 78
    • 2.LeetCode 416
      • 背包问题
      • 分割等和子集
    • 3.综合上述解题
  • 总结

前言

最近在准备蓝桥杯的过程中,遇到了一个题目。对于还是新手的我来说还是挺有难度的,我花了两天时间才比较清晰地解出来哈哈。这条题目还是涉及到了挺多内容的,我觉得这题不错,也想将解题思路保存下来,所以写此文章。解题的过程中参考了多篇优秀的文章,在此对其作者表示感谢。如果我没能清楚阐述出的地方,不妨读者亲自看看,必受益匪浅。


题目

问题描述:

逗志芃在干了很多事情后终于闲下来了,然后就陷入了深深的无聊中。不过他想到了一个游戏来使他更无聊。他拿出n个木棍,然后选出其中一些粘成一根长的,然后再选一些粘成另一个长的,他想知道在两根一样长的情况下长度最长是多少。

输入格式:

第一行一个数n,表示n个棍子。第二行n个数,每个数表示一根棍子的长度。

输出格式:

一个数,最大的长度。

样例输入:

4
1 2 3 1

样例输出:

3

数据规模和约定:

n<=15

刚开始遇到这个题目是真的没有思路。
要注意的是,在这个题目中,并不是所有的木棍都会用到,也就是说符合题意的是这些木棍的一个子集

解题思路

我在CSDN上看到了一位大佬的文章:蓝桥杯 无聊的逗 Python题解
其中的思路是:

LeetCode 78. 数列子集 + LeetCode 416. 分割等和子集
本题算是LeetCode 416的升级版吧,唯一的不同就是:不是考虑所有的数,而是可以有一些数不取。所以,我就想先求棍子数列的子集吧,再求子集的等和子集。

在上述文章中,提到了LeeCode的两道题。因为先前没有刷过这两题,所以我打算先把这两题找出来。

1.LeetCode 78

题目:LeetCode 78. 子集

大致题意是求出已知集合的所有子集。

可能确实是我实力不够哈哈,这道题我也没有思路。所以就进一步地去查找资料。
接着我就查到了下面这篇,文章思路清晰,非常受益!
Python算法——求集合的所有子集

在这篇文章中,大致有两种求已知集合子集的方式:

方法一:位图法

因为一个元素个数为n的集合,它的子集个数为 2 n 2^n 2n 。 此时假设存在一个n位2进制数,它的每一位对应原集合一个元素。对于每一位来说,为0时相当于该位对应的元素不在一个子集中,为1则在这个子集中。那么这个n位2进制数就可以表示原集合的各个元素在或不在一个子集中,从而表示这个子集。

如:有个集合为[0, 1, 2]
则2进制数011(3)即可表示子集[0, 1]。注意对应关系
由此思路可以写出代码:

def function_1 (input_list):n = len(input_list)for i in range(2 ** n): # 子集的个数,每次循环形成一个子集combo = []for j in range(n):if (i >> j) % 2:combo.append(input_list[j])print(combo)

方法二:迭代法

该方法的主要思路是先理清楚各个子集之间的迭代关系,再利用这个迭代关系来生成子集。

假设原始集合s = [1,2,3],
第一次迭代:<1>;
第二次迭代:<1,12,2>;
第三次迭代:<1,12,2,13,123,23,3>;
每次迭代,都是上一次迭代的结果+上次迭代结果中每个元素都加当前迭代元素+当前迭代元素

def function_2(input_list):combo = [[]]for i in input_list:combo = combo + [[i] + j for j in combo]for i in combo:print(i)

好,到目前为止,我们得到了原集合的所有子集,其中肯定包括了能够符合题意的子集。接着我们就需要判断的是,能够被划分成相等的部分的子集中即该子集中存在几个元素之和为该子集元素总和的一半,子集元素总和一半最大的是哪个子集。

2.LeetCode 416

题目:LeetCode 416. 分割等和子集
不出意料的,我还是没有思路。
于是我继续查找资料,并再次发现了宝藏文章:
动态规划之0-1背包问题(详解+分析+原码)
在该文章中,解释了分割等和子集其实就是背包问题的变型。
要想解决分割等和子集,则需要先学习背包问题。

背包问题

目前有N个价值和重量不同物品,并有容量为Room的背包。
求如何选择物品装入背包,能使背包中物品的总价值最大

如:该n个物品的价值分别为value = [1, 2, 3], 重量为wieght = [2, 3, 1]。背包的容量room为4。则当装入物品2和物品3时能够获得最大的价值总和5。
背包问题的实质是动态规划问题

我的理解是,动态规划问题的特点就是复杂问题的解决依赖于简单一级的问题即复杂问题可由简单问题推导解决。因此,解决复杂问题可由通过先解决简单问题,再通过推导关系推出复杂问题的解。

因此,我们可以先假设存在一个 (n+1)*(room+1) 的列表dp[i][j]+1是要考虑为0的情况
解决背包问题的关键在于理解这个列表横纵坐标的意义。

纵坐标i的意义是假设当前只考虑前 i 个物品。
横坐标j的意义是假设当前的背包容量为j。
dp[i][j]的值即是在考虑前 i 个物品且背包容量为 j 时,书包所能装下最大价值总和。

这样来看,大容量背包和多物品的问题,就变成了较小容量背包和较少物品的问题。
而我们要解决的问题实际上就变成了退出这个二维列表的各项值,最后一项值dp[n][room]就表示了我们所要求的。

动态规划问题英语:Dynamic programming,简称 DP的关键在以下三点:
1.定义状态
就像我上面所述的,建立一个列表,并确定列表各个坐标值的意义。

2.确定初始状态
在此背包问题中,初始状态就是当 i 或 j 为 0 时,显而易见背包装下的最大价值总和必为 0。在动态规划问题中,最初几项的值往往比较特殊,需要自行逻辑推理得出。至于如何确定一个动态规划问题有多少个初始状态,听说做多了相关题目就会有所经验。

3.推出状态转移方程
以这个背包问题为例,我们来推导状态转移方程:
设当前的坐标值为[i][j]。

如果是将第 i 个物品放入背包中,那么此时背包中的最大价值就相当于dp[i-1][j-weight[i]]+value[i]。理解这个式子的关键是,塞入第 i 个物品,背包容量变为j-weight[i],那么此时dp[i][j]就与dp[i-1][j-weight[i]]相关联了。

我们可以理解dp[i][j]dp[i-1][j]的区别是是否要将第 i 个物品放入背包中。
如果没有将第 i 个物品放入背包中,那么考虑前 i 个物品和考虑前 i-1 个物品实质上是一样的,即dp[i][j] = dp[i-1][j]

还有一种特殊情况是j < weight[i],那么无论如何第 i 件物品都不可能放入背包。

因此我们要确定第 i 间物品到底要不要放入背包中,就要对比dp[i-1][j-weight[i]]+value[i]dp[i][j] = dp[i-1][j]的大小关系,并取大者。
至此我们就得到了状态转移方程。

以下是dp表前几步的变化
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]

[0, 0, 0, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]

[0, 0, 0, 0, 0]
[0, 0, 1, 1, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]

'''理解背包问题的关键在于理解建立的表格横纵坐标的意义'''
'''是指当有前i件物品可选且背包容量为j时,第i件物品是否要放入'''
'''关键是定义好初始状态,然后对比判断是否要放入第i个物品'''
n = int(input())    # 可选物品的个数
room = int(input()) # 背包的容量
weight_list = list(map(int, input().split()))   # 各个物品的重量
value_list = list(map(int, input().split()))    # 各个物品的价值mid_map = [[0 for i in range(room+1)]for j in range(n+1)] # dp表for i in range(1, n+1):for j in range(1, room+1):if weight_list[i-1] > j:mid_map[i][j] = mid_map[i-1][j]else:mid_map[i][j] = max(mid_map[i-1][j], mid_map[i-1][j-weight_list[i-1]]+value_list[i-1])
print(mid_map[n][room])

至此,我们解决了背包问题。

分割等和子集

分割等和子集是背包问题的变型。

首先集合元素总和为偶数才存在分割。
求出集合元素的总和后,我们可以假设有一个背包的容量大小为该总和的一半,而各个元素看成价值等于重量的物品。
那么背包中的最大价值和必定小于或等于该总和的一半。如果存在几个元素的和为总和的一半,那么其他元素的和也为总和的一半。背包中的最大价值也为总和的一半。
利用与背包问题相同的解法求解,最后我们看dp的最后一项是否等于总和的一半便知道该集合是否能分割成等和子集了。

n = int(input())    # 集合元素的个数
input_list = list(map(int, input().split()))    # 待求的集合
m = sum(input_list) # 集合元素的总和
if m % 2 != 0:print('false')exit()mid_map = [[0 for j in range(m//2+1)] for i in range(n+1)]  # dp表
for i in range(1, n+1):for j in range(1, m//2+1):if input_list[i-1] > j:mid_map[i][j] = mid_map[i-1][j]else:mid_map[i][j] = max(mid_map[i-1][j], mid_map[i-1][j-input_list[i-1]]+input_list[i-1])
if mid_map[n][m//2] != m/2:print('false')
else:print('true')

至此,我们解决了分割等和子集的问题。

3.综合上述解题

就像最前面说的,无聊的逗这个问题的实质是求:

木棍各个能分割成等和子集的子集中,子集元素总和÷2的最大值是多少

可能听起来有点绕,可以结合代码理解。

pub_list = []def DenHeZiJi(input_list):list_len = len(input_list)if sum(input_list) % 2 != 0:    # 不能少了这一步,不然会出现意外的错误return 0n = sum(input_list)//2mid_list = [[0 for j in range(n+1)] for i in range(list_len+1)]for i in range(1, list_len+1):for j in range(1, n+1):if input_list[i-1] > j:mid_list[i][j] = mid_list[i-1][j]else:mid_list[i][j] = max(mid_list[i-1][j], mid_list[i-1][j-input_list[i-1]]+input_list[i-1])if mid_list[list_len][n] != n:   # 该子集不能再划分为等和子集return 0else:return n   # 该子集能够划分为等和子集def ZiJi(input_list):   # 求出结合的所有子集global pub_listn = len(input_list)combo = [[]]    # 储存所有子集的列表for i in input_list:combo = combo + [[i] + j for j in combo]for i in combo:pub_list.append(DenHeZiJi(i)) # 判定每个子集能否划分为等和子集,并储存该子集元素总和一半的值N = int(input())
ori_list = list(map(int, input().split()))
ZiJi(ori_list)
print(max(pub_list))

至此,解决完所有问题。


总结

个人认为这道题具有一定难度,且涉及到多条经典题目的联合求解,值得单独拿出来记录,对于动态规划的理解很有帮助。也让我认识到了自己的水平有多么的不足,更应该努力备赛。

如果这篇文章对你有所帮助,请你点个赞哈哈。
如果你发现有哪里存在纰漏,也请不吝赐教。
感谢你的阅读。

【蓝桥杯Python-无聊的逗】解题思路和参考代码相关推荐

  1. [蓝桥杯python] 无聊的逗:逗志芃在干了很多事情后终于闲下来了,然后就陷入了深深的无聊中。不过他想到了一个游戏来使他更无聊。他拿出n个木棍,然后选出其中一些粘成一根长的,然后再选一些粘成另一个长

    [蓝桥杯python] 无聊的逗 问题描述 1.资源限制 2.输入格式 3.输出格式 4.样式输入及输出 5.代码及解析 大功告成!编写不易,大家成功后点个关注or赞谢谢~~ 问题描述 逗志芃在干了很 ...

  2. 蓝桥杯 ALGO-1004 无聊的逗 01背包+回溯 python

    题目 这逗志芃也太无聊了吧,玩这么一个游戏- 为了这道题能拿个AC我花了三天的时间才搞清楚,感觉这蓝桥杯还是有点难度啊

  3. 蓝桥杯 Python 算法训练 逗志芃的暴走

    问题描述 逗志芃是有妹子的现充,但是有时候妹子就是烦恼.因为逗志芃太逗了,所以这段时间妹子对逗志芃发动了技能无理取闹,妹子要去玩很多的景点.由于逗志芃之前抽机花费了太多的时间,不久以后又要微积分考试了 ...

  4. 蓝桥杯单片机十一届省赛_“第十届蓝桥杯青少组C++省赛”前三题解题思路及参考答案!...

    关注 及时了解信息学竞赛相关资讯 第十一届蓝桥杯青少组C++省赛即将到来,待考的各位小同学有没有刷完去年的真题呢?以下是为大家复习准备的"第十届蓝桥杯青少组C++省赛"前三题解题思 ...

  5. 2022年蓝桥杯Python程序设计B组思路和代码分享

    2022年蓝桥杯Python程序设计B组比赛结束了,分享一下题目以及思路. 文章目录 A:排列字母 题目: 思路: 代码: B: 寻找整数 题目: 思路: 代码: C: 纸张尺寸 题目: 思路: 代码 ...

  6. 蓝桥杯Python初级组测试题之Turtle画图2

    蓝桥杯Python初级组测试题之Turtle画图2 1. 画8个内接正五边形 题目描述 解题思路 (1) 共有n=8个正五边形,因而有8次循环,每次旋转角度增加ang=360/n (2) 画内接正五边 ...

  7. 第十三届蓝桥杯Python B组国赛题解

    第十三届蓝桥杯Python B组国赛题解 试题A:斐波那契与7 试题 B: 小蓝做实验 试题 C: 取模 试题 D: 内存空间 试题 E: 近似 GCD 试题 F: 交通信号 试题 G: 点亮 试题 ...

  8. 2021-04-24 蓝桥杯 Python 第五题--密室逃脱

    Python 2021-04-24 蓝桥杯 第五题解题思路 密室逃脱 提示信息: 游戏规则: 编程实现: 输入描述 输出描述 解题思路 解法一 解法二 解法三 2021-04-24 蓝桥杯Python ...

  9. 第十三届蓝桥杯Python 大学B组真题详解

    第十三届蓝桥杯Python B组真题详解 试题A 排列字母 试题B 寻找整数 试题C 纸张尺寸 试题D 位数排序 试题E 蜂巢 试题F 消除游戏 试题G 全排列的价值 试题H 技能升级 试题I 最长不 ...

最新文章

  1. 从工程中删除Cocoapods
  2. CAS集成oauth2协议的支持
  3. CentOS 7.2 时间同步与时区设置
  4. 学长毕业日记 :本科毕业论文写成博士论文的神操作20170329
  5. Qt Creator连接设备
  6. android addView的使用
  7. 开源.NET企业级应用系统 OpenVista
  8. allow php tag,Smarty allow_php_tag报告'未定义的属性:Smarty :: $ allow_php_tag'
  9. ubuntu 打开ssh登陆_Ubuntu开启SSH远程登录
  10. Spring Data Jpa使用篇
  11. 【181128】VC++ MFC编写桌面放大镜工具源代码
  12. zabbix监控系统
  13. 天玑处理器排行榜2022 联发科天玑处理器性能排行榜2022
  14. Qemu core 调试Cannot access memory at address 0x7fbc6c792858
  15. 5455. 最多 K 次交换相邻数位后得到的最小整数
  16. 交大研究生去日本签证攻略
  17. U-BOOT启动流程详细分析[转]
  18. pandas分组聚合
  19. 码农小白 设计模式篇 状态模式
  20. Python量化编程如何判断均线数据是金叉还是死叉?-股市数据均线策略编程分析

热门文章

  1. 有效处理Java异常三原则
  2. 线阵相机的线扫描速率的计算方法
  3. Scratch中的变量
  4. JQ实现星星评价(带半星)
  5. 在线JS运行 JavaScript IDE
  6. Xmind基础教程-保存到印象笔记
  7. Excel与PowerBI 之PowerQuery 编辑界面异同-PowerQuery 系列文章之三
  8. .md文件转word或PDF
  9. html多行多列的表单,如何制作多行多列的表格
  10. 网络工程师的前景如何?未来是怎么样的?