蓝桥杯《程序设计竞赛专题挑战教程》第二章学习

写在前面

这个专栏是我自己备赛蓝桥杯的过程中写的一些心得,本人比较菜,学习新的东西比较慢,但是如果自己有一些好的点子肯定会写下来然后共同学习

第二章——手算题和杂题

做题巧思

        在本书中提到了四种在解决竞赛中填空题的技巧,分别是“巧用编辑器”、“眼看手数”、“巧用Excel”、“巧用Python”。这几个方法用来应付竞赛是非常不错的,但是秉持着提高代码水平的思想,在这一章中我尝试着尽力使用代码来解决所有问题,由于我报名的是Python赛道,所以都使用的是Python语言来解决。

        如果遇到了不会的题目我会看书上提供的思路,然后自己手敲一遍,但是会清晰地把思路的过程重新呈现一遍,说这一点的愿意是,这些字一定是我一个一个敲上去的,没有复制粘贴的嫌疑,当然,如果有自己的思路也会给大家展示出来。

问题呈现

2.1 门牌制作

从1到2020的所有数字中,共有多少个2

re = 0

for i in range(1, 2021):
    for s in str(i):
        if s == '2':
            re += 1

print(re)

这道题应该是比较简单的,遍历[1, 2020],然后将每一个元素转化成字符串,再对字符串进行遍历,如果出现了 '2' 则给结果 +1

这里要注意两点:

1、range()方法的范围是左闭右开,本题中range方法取到的范围是[1, 2021)

2、遍历字符串过程中,需要对 '2' 进行判断,而不是 2 

2.2 卡片

小蓝有很多数字卡片,每张卡片上都是0到9的数字。小蓝准备用这些卡片来拼一些数。他想从1开始拼出正整数,每拼一个,就保存起来,卡片就不能用来拼其他数了。小蓝想知道自己能从1拼到多少。例如,当小蓝有30张卡片,其中0到9各3张则小蓝可以拼出1到10,但是拼11时卡片1只有一张了,不够拼出11现在小蓝手里有0到9的卡片各2021张,共20210张,请问小蓝可以从1拼到多少。

a = [2021] * 10
flag = 1
i = 0

while flag:
    i += 1
    j = str(i)
    for t in j:
        b = int(t)
        if a[b] == 0:
            flag = 0
            break
        else:
            a[int(t)] -= 1

print(i - 1)

这道题的解题思路和上一道题类似,主要过程为将数字转化为字符串,然后对其中出现的某个数字的次数进行统计。

其中,a = [2021] * 10 为列表操作,效果为产生给列表a中保存10个2021,也就是将2021在列表a中复现十次。变量 flag 用来保存当前循环是否可以继续进行,变量 i 用来保存当前进行统计的数字。

在循环中,首先对 i 进行自增,然后用 j 来保存转化为str类型的 i 。之后对 j 进行遍历,将每一个元素再转换成int类型,接着访问数组 a,将对应的索引下的数字的数量-1。在减少数量之前需要对该索引下的数字进行判断,如果当前索引的数字为 0 那么则退出循环,同时将 flag 赋值为 0,退出外层循环。

最终输出 i 时需要 -1,是为了避免这种情况。例如在某种情况下,最后一个检索的数字为 11,但是当前 1 的数量为 1,也就是只能检索一个 1,当检索完第一个 1 之后,1 的数量变成了 0,第二个数字无法检索,但是此时 i 保存的是 11,答案发生了错误,所以需要 -1 来保证答案的正确。

2.3 迷宫

给出一个迷宫,问迷宫内的人有多少能走出来。迷宫的每个位置上有一 个人,共100人,每个位置有指示牌,L表示向左走,R表示向右走,U表示向上走,D表 示向下走。迷宫地图如下:
mp =[
    "UDDLUULRUL",
    "UURLLLRRRU",
    "RRUURLDLRD",
    "RUDDDDUUUU",
    "URUDLLRRUU",
    "DURLRLDLRL",
    "ULLURLLRDU",
    "RDLULLRDDD",
    "UUDDUDUDLL",
    "ULRDLUURRR"
]
def dfs(x, y):
    if x < 0 or y < 0 or x >= 10 or y >= 10: return 1
    if vis[x][y] == 1: return 0
    vis[x][y] = 1
    if mp[x][y] == "L": return dfs(x, y - 1)
    if mp[x][y] == "R": return dfs(x, y + 1)
    if mp[x][y] == "U": return dfs(x - 1, y)
    if mp[x][y] == "D": return dfs(x + 1, y)

mp =[
    "UDDLUULRUL",
    "UURLLLRRRU",
    "RRUURLDLRD",
    "RUDDDDUUUU",
    "URUDLLRRUU",
    "DURLRLDLRL",
    "ULLURLLRDU",
    "RDLULLRDDD",
    "UUDDUDUDLL",
    "ULRDLUURRR"
]
ans = 0
for i in range(10):
    for j in range(10):
        vis = [[0] * 10 for _ in range(10)]
        if dfs(i, j) == 1:
            ans += 1
print(ans)

这道题涉及到DFS算法,但是作为一道填空题可以利用“眼看手数”的方式来完成。

在“眼看手数”过程中,为了提高速度,有几个小技巧

1、如果R右边是L则这两个一定不能出迷宫;同理,如果出现L左边是R,D下边是U,U上边是D,这些情况都可以直接判定无法出迷宫;

2、可以将不能出迷宫的元素标记下来,如果遇到别的元素走到这个元素上,那么表示一定不能走出迷宫;

3、作为一道填空题没有必要强求自己利用DFS算法做出来(说到底还是自己不会,我会继续好好学的)

2.4 七段码

七段数码管一共有7段可以发光的二极管,分别标记为a、b、c、d、e、f、 g问能表示多少种不同的字符,要求发光的二极管是相连的。

对于本题,依旧是利用“眼看手数”的方式,我选择了一个比较笨但是很有效的办法。

a、b、c、d、e、f、 g七个字母的所有组合方式写出来,然后依次判断这些二极管是否相连。

最终根据我的不懈努力,结果为80。其中,亮一个灯有7种,亮两个灯10种,亮三个灯有16种,亮四个灯有20种,亮5个灯有19种,亮6个灯有7种,亮七个灯有1种。

在列举过程中,为了保证不会列举到相同的情况,需要保证字母是一次增大的,也就是说不会出现 acb 或者 bac 这些情况,这样可以减少计算次数也可以保证答案的正确性。

2.5 分数

1/1+1/2+1/4+1/8+.…,每项是前一项的一半,如果一共有20项,求和是多少,结果用分数表示出来。分子分母要求互质。

m = pow(2, 19)

re = 0

for i in range(20):
    mm = pow(2, i)
    re += m/mm

print(int(re), '/', m)

这道题很好理解,就是计算\sum_{0}^{n}1/2^{_{}^{i}},其中n=19,题目指明一共有20项,即n的范围为[0,20)。

这道题的思路为将每一项转换为分母为2^{_{}^{19}}的分数,然后将其分子相加,再输出最终结果即可。每一项的分子也比较好确定,即2_{}^{19} / 2_{}^{i},最终累加即可。

2.6 日期问题

整个20世纪(1901年1月1日至2000年12月31日),一共有多少个星期一?

本题有两种方法,方法一:

from datetime import *

dt1 = datetime(1901, 1, 1)
dt2 = datetime(2000, 12, 31)

td = dt2 - dt1

print(td.days//7)

方法一使用了python自带库datetime,这个库中封装了datatime类,用于保存日期和时间。其构造方法如下。

def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0,
                microsecond=0, tzinfo=None, *, fold=0):

datetime 库对减运算进行了重写,二者相减之后保存到变量 td 中,此时变量 td 的类型为 timedelta,这个类用于保存时间间隔,即两个时间点之间的时间长度。在 timedelta 对象中存在属性 day,即两个时间点之间相差的天数。同理,还存在 seconds、minutes、hours 三种属性,分别用来保存两个时间点之间相差的秒数、分钟数、小时数。

可以参考该链接进行学习Python时间模块之datetime模块

在求出两个时间点之间有多少天之后,将这个天数对 7 进行整除,最终可以输出一共正好有几个星期,即就是星期一的数量。


方法二:

s = 0
for i in range(1901, 2001):
    if (i % 4 == 0 and i % 100 != 0) or i % 400 == 0:
        s += 366
    else:
        s += 365
print(s//7)

与方法一思路相同,中间多了一块判断某一年是否是闰年,没有合理利用python的特性。

2.8 顺子日期

小明特别喜欢顺子。顺子指的就是连续的3个数字:123、456等。顺子日期 指的就是在日期的 yyyymmdd 表示法中,存在任意连续的三位数是一个顺子的日期。例如 20220123 就是一个顺子日期,因为它包含一个顺子123;而20221023 则不是一个顺子日期, 它一个顺子也没有。小明想知道在2022年一共有多少个顺子日期。

from datetime import *

dt1 = datetime(2022, 1, 1)
re = 0

for i in range(365):
    t = str(dt1)
    tt = t[0:4] + t[5:7] + t[8:10]
    if '012' in tt or '123' in tt or '234' in tt or '345' in tt or '456' in tt \
            or '567' in tt or '678' in tt or '789' in tt:
        print(tt)
        re += 1
    dt1 += timedelta(days=1)

print(re)

本题还是考察对 datetime 库的使用,而且本题的思路也较为简单,遍历2022年的每一天,判断每一天的日期中是否存在“顺子”。

首先,定义2022年的第一天(也就是一个datetime变量);其次。对2022的每一天进行判断,具体过程为,先将每一天的日期转化成 str 类型,也就是代码中的变量 t,此时 t = 2022-12-31 00:00:00,再利用 python 中的切片操作,将年月日拼接成题目所要求的格式,最后列举所有顺子,判断该字符串中是否存在符合要求的顺子。

其中有一句关键代码

dt1 += timedelta(days=1)

首先,我们知道 dt1 为 datetime 类型,表示具体的每一天;而 timedelta 变量表示时间间隔,将 timedelta 的构造函数中的 days 初始为 1,则表示这个时间间隔为一天,然后增加到原来的日期上,就使得每一次循环都是第二天,一共循环 365 次,则将这一年所有的日子都遍历了一遍。

2.9 特殊时间

2022年2月22日22:20 是一个很有意义的时间,年份为2022,由3个2和 1个0组成,如果将月和日写成4位,为0222,也由3个2和1个0组成,如果将时间中的时和分写成4位,还由3个2和1个0组成。小蓝对这样的时间很感兴趣,他还找到了其他类似的例子,如111年10月11日01:11、2202年2月22日22:02等。请问,总共有多少个 时间是这种年份写成4位、月日写成4位、时间写成4位后均由3个一种数字和1个另一种数字组成的。注意1111年11月11日11:11不算,因为它里面没有两种数字。

本题要求列举出所有符合题目规则的日期及其时间,根据该时间的规律,我认为用直接计算的方法会更快一点。

首先,题目要求的“有意义的时间”为:年(3x+y的自由组合)月日(3x+y的组合)时分(3x+y的组合)。我们可以对年份的书写进行分类(即对x和y进行分类)。

1、x = 0 这种情况是不可能的。让年份出现三个 0 是轻而易举的,但是月日无法出现三个 0(不存在零月一日的情况);

2、x = 1 这种情况比较复杂。让年份出现三个 1 和一个除过 1 以外的任意值是轻而易举的,所以下面不对年份进行讨论;

        y = 0:对于年来说,就是 0 放哪里的问题,即A_{3}^{3};对于月日来说,当月份为 01 或 10 时,日期只能是 11;当月份为 11 时,日期可以是 01 或 10,即 2+2 = 4;对于时分来说,与月日同理,当小时为 01 或 10 时,分钟只能是 11;当月份为 11 时,日期可以是 01 或 10,即2+2 = 4;

(由于篇幅,下面我可能会用一些简便的方式来表达我的计算过程)

        y = 2:年->A_{3}^{3};月(12 / 11)日(11 / 12、21) ->1+2 = 3;时(12 / 21 / 11)分(11 / 11 / 12、21)->1+1+2 = 4;

        y = 3:年->A_{3}^{3};月(11)日(13)->1+1 = 2;时(11 / 13)分(13、31 / 11)->2+1 = 3;

        y在[3, 5]这个范围之间其实是同理的,因为月份最大到 12,所以月日的组合中,月只能是 11 那么日必然是1y,而时分的组合不受影响,因为小时最大可以是23,分钟最大可以是59,故这一部分省略;

        y = 6:年->A_{3}^{3};月(11)日(16)->1+1 = 2;时(11 / 16)分(16 / 11)->1+1 = 2;

        同理,y在[6, 9]这个范围内也是一致的,月日的组合就不再重复了;对于时分的组合,小时依旧有两个形式,而分钟只能是1y,因为分钟最大为59,不能存在y1这种情况。

3、x = 2 这种情况比较复杂。让年份出现三个 2 和一个除过 2 以外的任意值是轻而易举的,所以下面不对年份进行讨论;

        y = 0:年->A_{3}^{3};月(02)日(22)-> 1;时(02 / 20 / 22)分(22 / 22 / 02、20);1+1+2 = 4;

        y = 1:年->A_{3}^{3};月(12)日(22)-> 1;时(12 / 21 / 22)分(22 / 22 / 12、21);1+1+2 = 4;

        当y = 3时,对于月日的组合,无法安排三个 2 一个 3,无论怎么安排都会超出实际情况,故当y > 3就不存在任何组合符合题意。

4、x = 3 与y = 3同理,年份是可以组合的,但是无法让三个 3 出现在月日中。

最终结果为 212

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值