2021年软件类第十二届蓝桥杯 省赛 python组 A-E题解


文章目录

  • 2021年软件类第十二届蓝桥杯 省赛 python组 A-E题解
  • 试题 A:卡片
  • 思路
  • 代码
  • 答案
  • 试题 B:直线
  • 思路
  • 代码
  • 答案
  • 试题 C:货物摆放
  • 思路
  • 代码
  • 答案
  • 试题 D:路径
  • 思路
  • 代码
  • 答案
  • 试题 E:回路计数
  • 思路
  • 代码
  • 答案



备战蓝桥杯的时候,记录一下写的题目和一些思考和理解

这里面的题目都可以从https://www.lanqiao.cn/courses/2786/learning/?id=280833得到,写了一些python的解法
除了本身的题解之外,也可以配套视频看一下吧,在我空闲之余做了这个的讲解,希望对大家有帮助,B站讲解视频

试题 A:卡片

蓝桥杯历年习题python 蓝桥杯python省赛历年真题_动态规划

思路

对于这道题来说,我局的思路还是比较清晰的,我们首先知道自己的卡片个多少张,我们可以利用一个哈希表,只要我们在拼一个数字的时候,我们的哈希表的值为0了,也就是我们的客片不够了,我们就直接输出我们的结果

对于python来说,我们可以很容易的去用我们的字典,我们的字典就是我们的哈希表,代码还是很简单的

代码

# https://www.lanqiao.cn/problems/1443/learning/
h = {}
n = 2021 # 卡片的数目
for i in range(10):
    h[str(i)] = n # 定义每张卡片的数目

flag = True
i = 0
while flag:
    i += 1
    for x in str(i):
        h[x] -= 1 # 拼x这个数需要用掉一张卡片
        if h[x] < 0: # 卡片少于0,说明第i张卡片拼不了
            flag = False
            break
    
print(i-1)

答案

3181

试题 B:直线

蓝桥杯历年习题python 蓝桥杯python省赛历年真题_动态规划_02

思路

对于这道题来说,我们就需要根据我们学的数学公式,也就是我们的斜率和截距公式

对于每一条直线来说,最重要的就是他的斜率和截距,如果我们单单以斜率来看的话,就忽略了截距,所以两者都需要考虑,除此之外,我们还需要考虑的就是特殊情况,即斜率不存在的情况

所以我们就可以遍历所有的点,对于我们的任意两点是可以组成一条直线的,我们记录他的斜率和截距,最后我们再加上我们没有计算的斜率不存在的情况,斜率不存在的情况也很简单,就是我们的x坐标的范围,在这道题就是20。

代码

#直线
points=[[x,y] for x in range(20) for y in range(21)] #创建二维列表:代表xy坐标系
docker=set()                            #创建集合属性的容器:因为集合里的元素不会重复
for i in range(len(points)):            #二重循环遍历每个坐标
    x1,y1=points[i][0],points[i][1]     #注意书写格式:a,b=c,d
    for j in range(len(points)):
      x2,y2=points[j][0],points[j][1]
      if x1==x2:                        #特殊情况:直线垂直时斜率不存在,先跳过最后计算
          continue
      k=(y2-y1)/(x2-x1)                 #斜率公式
      b=(x2*y1-x1*y2)/(x2-x1)           #截距公式
      if (k,b) not in docker:           #存入容器里没有的(斜率,截距)对
          docker.add((k,b))
print(len(docker)+20)                   #输出结果:容器的长度40237+斜率不存在的20种情况=40257

答案

40257

试题 C:货物摆放

蓝桥杯历年习题python 蓝桥杯python省赛历年真题_蓝桥杯_03

思路

这道题说简单也简单,不简单也不简单,如果用穷举的方法,用python不知道要猴年马月才能算出来

所以我们就要用聪明一点的方法,比如我们可以首先得到我们的n的约数,之后来说,就是这些约数的组合了,因为我们肯定要保证长宽高是我们的n的约数的,要不然不可能可以满足题目条件

接着我们就可以对约数进行一个组合,如果组合结果相乘是n的话,我们就可以把方案输出

tips:其实对于这道题来说,我们还可以利用分解质因数的方法,这个方法就可以在1s内输出结果,简单来说,就是会对我们的n分解质因数,对于分解的质因数我们也可以进行组合变成我们的因数,最后结果相乘就是我们的n,因为都是分解质因数得来的结果。

代码

n=2021041820210418              #货物数量
ans=0                           #初始统计值为0
docker=set()                    #创建集合属性的容器
for i in range(1,int(n**0.5)+1):#循环遍历,筛选n的约数(对n开根号是为了加快速度)
    if n%i==0:
        docker.add(i)           
        docker.add(n//i)
for i in docker:                #三重循环遍历容器(放心,三重不慢,运行5s就出结果)
    for j in docker:
        for k in docker:
            if i*j*k==n:        #满足条件,方案数+1
                ans+=1
print(ans)                      #输出结果:2430

答案

2430

试题 D:路径

蓝桥杯历年习题python 蓝桥杯python省赛历年真题_蓝桥杯历年习题python_04

思路

这道题还是比较简单得到,我们可以看到这个是一个无向图,我们可以直接用动态规划的思想,也就是DP的思想来解决,首先我们可以看到条件,两个节点,如果差的绝对值大于21,就无边先连,否则,就用长度为最小公倍数的无向边相连。

我们求的是最短路径,首先我们就可以遍历差为21的数,这里就是用二层循环表示,最后我们再用dp的方式,这个动态转移的方法还是简单的,两种情况,直接用当前的数,或者加上最小倍数的长度,这两者的最小值就是我们的最短路径的长度
蓝桥杯历年习题python 蓝桥杯python省赛历年真题_蓝桥杯_05

代码

import math

# 最小公倍数模板
def lcm(a,b):
    return a*b//math.gcd(a, b)# 利用内置函数

dp = [float('inf')]*(2021+1)

dp[1] = 0 # 节点从0开始
for i in range(1,2021+1): # 1 -> 2021
    for j in range(i+1,i+21+1): # 差为21
        if j > 2021:
            break
        dp[j] = min(dp[j],dp[i]+lcm(i, j)) # 动态转移方程
print(dp[2021])

答案

10266837

试题 E:回路计数

蓝桥杯历年习题python 蓝桥杯python省赛历年真题_python_06

思路

这道题主要的思路就是状态压缩的动态规划,简称状压DP
简单来说,我们可以用二进制来代表我们的21个状态,加入我们有4栋教学楼,那么我们访问所有教学楼就是1111(二进制),初始状态是从第一个教学楼走的,也就是初始状态为1000,利用这样的方法,如果有n栋教学楼,我们就有1<<n种状态,这道题就是2的21次方个状态

除此之外,我们还需要一个记录两个教学楼是否互通,就是判断两者的最大公因数是否为1

最后我们就可以用这道题的核心,也就是我们状态压缩DP,对于这个状压DP来说,我们的想法就是,蓝桥杯历年习题python 蓝桥杯python省赛历年真题_蓝桥杯_07对于状态i,i的二进制表示中为1的位置,表示走过了教学楼 j

我们会枚举每一个状态,对于每一位来说,我们都会判断状态i是否包含第j栋教学楼,如果包含的话,就会进行判断其他位,枚举所有可能从教学楼k走到教学楼j的情况,加入我们的状态i除去j后还包含j的话,我们就可以加入其中的方案
蓝桥杯历年习题python 蓝桥杯python省赛历年真题_斜率_08
最后我们就可以得到所有的方案,不过要减去j状态为0时候的方案,因为我们一开始就是从0,也就是第一件教学楼出现的。

代码

import os
import sys
from math import gcd

n = 21
m = 1 << n # 一共有2的21次方个状态
dp = [[0]*n for i in range(m)] # dp[i][j]对于状态i,i的二进制表示中为1的位置 表示走过了教学楼j
load = [[False]*n for i in range(n)] # 存储i, j之间是否有路
for i in range(1,n + 1):
    for j in range(1,n + 1):
        if gcd(i, j) == 1:
            load[i-1][j-1] = True
dp[1][0] = 1 # 表示初始状态,也就是第二个状态 0000...0001 第一位为1
for i in range(1,m): # 枚举每一个状态 
    for j in range(n): # 对每一位 判断状态i是否包含第j栋教学楼
        if i >> j & 1: # 如果第j+1位为1
            for k in range(n): # 判断其他位 枚举所有可能从教学楼k走到教学楼j的情况
                if (i - (1<<j)) >> k & 1 and load[k][j]:  # 判断状态i除去j后是否包含k
                    dp[i][j] += dp[i-(1<<j)][k]
print(sum(dp[m-1]) - dp[m-1][0])

答案

881012367360