蓝桥杯2021省赛Python

不得不说2021的比2020的难

1.卡片

python turtle 蓝桥杯 蓝桥杯python组难吗_2d

很明显,最先没的肯定是1或者0,我们只要统计到谁会消耗2022个1或者0就好了

if __name__ == '__main__':
    res1 = 0
    res0 = 0
    for i in range(1,100000):
        i = str(i)
        res1 += i.count('1')
        res0 += i.count('0')
        if res1 == 2022 or res0 == 2022 : 
            break
    print(int(i)-1)

# 3181

2.直线

python turtle 蓝桥杯 蓝桥杯python组难吗_2d_02

两点式 (y1-y2)X + (x2-x1)Y + (x1y2-x2y1) = 0

那么我们只要把每两个点都遍历一遍,然后约分不同的加入进我们的集合就好了

(y1-y2) * x +(x2-x1) * y +( x1 * y2 - x2 * y1)=0 两点式求方程

def gcd(a,b):
    if b == 0: return a
    else: return gcd(b,a%b)
if __name__ == '__main__':
    dian = []
    for i in range(20):
        for j in range(21):
            dian.append((i,j))
    res = set()
    for i in range(len(dian) - 1):
        x1,y1 = dian[i]
        for j in range(i+1,len(dian)):
            x2,y2 = dian[j]
            A,B,C = y2-y1,x2-x1,x1*y2-x2*y1
            k = gcd(gcd(A,B),C)  # 求三个数的最大公约数

            res.add((A/k,B/k,C/k))
            
    print(len(res))

3.货物摆放

python turtle 蓝桥杯 蓝桥杯python组难吗_职场和发展_03

首先我们可以看到他的那些因子一定是他可以除得尽的。先预处理出来,然后去遍历

注意一点的是如果你从1到n去预处理他的因子的话会很慢,所以我们可以只处理到sqrt(n)

import math
yin = []
n = 2021041820210418
# for i in range(1,n+1):  # 太慢了
#     if n % i == 0:
#         yin.append(i)
for i in range(1,int(math.sqrt(n) + 1)):
    if n % i == 0:
        yin.append(i)
        yin.append(n/i)

print('over')


res = 0
for i in yin:
    for j in yin:
        for k in yin:
            if i * k * j  == n:
                res += 1
print(res)

4.路径

python turtle 蓝桥杯 蓝桥杯python组难吗_python turtle 蓝桥杯_04

很简单的一个动态规划问题,一个点到1的距离等于他前面21个任意一点到1的距离加上那个点到他的距离,我们只需要找到遍历前面21个点,找到最小值就好了。

这样一层一层最小下来,到最后一定是最小的。

def gcd(a,b):
    if b == 0: return a
    return gcd(b,a%b)
def gbs(a,b):
    return a*b//gcd(a,b)  
inf = 1e9
dp = [inf] * 2022  # dp表示到1的距离
for i in range(1,23):
    dp[i] = i

for i in range(23,2022):
    for j in range(1,22):
        dp[i] = min(dp[i],dp[i-j] + gbs(i,i-j)) # 后面的到1的距离等于前面21中的一个到1的距离加上前面21个中一个到他的距离
print(dp[2021])
PS:好像math库里面有求最大公约数的函数,下次直接用就好了别自己定义了,怕大家手滑写错了

5.回路计算

python turtle 蓝桥杯 蓝桥杯python组难吗_职场和发展_05

你看总共就21个楼,我们可以用二进制来表示,比如状态11101就表示去了1,3,4,5楼。

设置一个dp,dp[i][j] 就表示在状态i下能到达j的总数量,你看这个状态去了就例如 i = 11101, j = 3,就是到达j+1 = 4楼的总数量(为什么要+1,因为我们用j = 0 表示到达了第一楼,后面我们是用i >> j & 1来表示能不能到达这个楼,所以就定义为j+1了,如果要j是到达j楼的话 ,i需要多开一位就更大了)

那么要怎么算到第四栋楼的数量呢,再看这个状态11101,发现他可以有1,3,5栋楼可以走,那么我们就遍历这三栋楼就好了,就是把到第四楼的那个i变成0,然后到达剩下三栋楼。

假设遍历到k ,dp[i][j] += dp[i - (1<<j)][k]

这样状态转移是对的,因为他每次用到的i都是比他小的,在之前肯定是遍历过的,不会出现漏的情况

import math
n = 21
m = 1<<21
dp = [[0] * n  for _ in range(m+1)] # do[i][j]表示状态i到到达j的总数量
g =[[0]*n for _ in range(n)]  # g[i][j] 表示I能到J


for i in range(1,n+1):
    for j in range(1,n+1):
        if math.gcd(i,j) == 1: 
            g[i-1][j-1] = 1   
dp[1][0] = 1
# 如果要刚好dp[i][j] 表示第i个状态到第j个楼的话,要开22位的二进制,会更慢.所以我们j就表示J+1楼
for i in range(1,m):
    for j in range(n):
        if i >> j & 1:  # 判断j+1楼是否在里面
            for k in range(n):
                if i - (1 << j) >> k & 1 and g[k][j] == 1: #找到符合条件的k
                    dp[i][j] += dp[i - (1 << j)][k]

print(sum(dp[m-1]) - dp[m-1][0])  # 最后就是状态011111111111(21个1)到达不是1楼的就好了,因为每个楼都可以去1楼

6.时间显示

python turtle 蓝桥杯 蓝桥杯python组难吗_职场和发展_06

首先先找到那一天的毫秒数,然后再找小时数,以此类推,总共的毫秒数 - 小时的毫秒数 = 分钟数 + 减去小时和分钟剩下的毫秒数

n = int(input())
n = n%(24 * 60 * 60 * 1000) # 抛去前面N天的,剩下当天的毫秒数
hour = n // (60 * 60 *1000)
mins = (n - hour*60*60*1000) // (60 * 1000)
scent = (n - hour * 60 * 60 *1000 - mins * 60 * 1000) // 1000
print('%02d'%hour,end = ':')
print('%02d'%mins,end = ':')
print('%02d'%scent,end = '')

7.杨辉三角形

python turtle 蓝桥杯 蓝桥杯python组难吗_蓝桥杯_07

这题最开始我只写了一个时间复杂度高的,应该能得前面那个分,比较好理解,可以先看看。

if __name__ == '__main__':
     n = int(input())
     if n == 1: print(1)
     res = 3 # 已经计算的个数
     li,l = [1,2],3  # 第三行的一半元素和行数
     while n not in li:
         res += len(li)*2 - l % 2
         li = [1] + [li[i] + li[i+1] for i in range(len(li) - 1)] + ([li[-1]*2] if l % 2 == 0 else []) 
         l += 1
     print(res + li.index(n) + 1)

就是一行一行的去遍历,直到找到我们需要的

第二种是我觉得我这个太慢了,去看大佬的,不得不说太强了。。

思路参考:(156条消息) 备战蓝桥杯历年试题:杨辉三角形 省赛B组 Python详解_m0_62277756的博客-CSDN博客

但是其中还是有好像错误的点,我先在这里指一下,不对的话可以在评论区告诉我。。(当然代码没错)大佬在遍历斜行的时候,说从C(2i,i)找到C(2i,N),中有没有我们的答案,但我看了一下应该是从C(2i,i)到C(N,i)因为C(N,1)肯定是一个我们要的答案了。C(N,i)会大于他,因为这个点我当时想了蛮久的。。希望大家看的时候可以注意到这个点。能发现这个规律真的很厉害了

def c(a,b):
    res = 1
    i,j = a,1
    while j<=b:
        res = res * i / j
        i -= 1
        j += 1
    return int(res)

def find(j,n):  # 在第j斜行找到n的位置
    l,r = 2*j,n # C(a,j)的a最小是2*j 最大是n,因为C(n,1)够了已经
    while l <= r:
        mid = (l+r)//2
        if c(mid,j) == n:
            print(int(mid*(mid+1)/2) + j + 1) # j+1不能在里面,否则会被忽略
            return True
        elif c(mid,j) > n:
            r = mid - 1
        else: l = mid + 1
    return False

if __name__ == '__main__':
    N = int(input())
    for i in range(16,-1,-1):
        if find(i,N): break

8.左孩子右兄弟

python turtle 蓝桥杯 蓝桥杯python组难吗_职场和发展_08

这题其实我感觉跟我们学的左孩子右兄弟没什么关系啊,你只要把兄弟都变成你的右孩子,然后把你的孩子变成你的左孩子。那么我们就可以首先推出,如果你有3个孩子,那么以你为根的树高就是3+3个孩子的树高的最大值。### 因为你可以把三个孩子的其中一个变为左孩子,然后剩下两个其中一个变为那个左孩子的右孩子,然后剩下一个变为左孩子的右孩子的右孩子(画个图会好很多,建议去画画)然后我们只要找到三个孩子最高的树,让他当做最后一个孩子,那整体的树肯定就最高啦,并且会等于3+3个孩子树高的最大值,我们dfs搜索一下就好了。

没有孩子的高度就是0
ans = 0 
# f[u]以u结点为根的树的最大高度是u的子节点数量 + 各个子树的最大高度
# 其实就是看子树有多少个孩子,递归下去
def dfs(u): # 
    global ans
    tot = len(s[u])
    f[u] = tot
    res = 0
    for i in range(tot):
        v = s[u][i]
        m = dfs(v)
        res = max(res,m)
    f[u] += res
    return f[u]
        

if __name__ == '__main__':
    n = int(input())
    s = [[] for i in range(n+1)]
    for i in range(n-1):
        s[int(input())].append(i+2) 
    # 输入 5 1 1 1 2  那就是 s[1] = [2,3,4] 表示 2,3,4是父节点是1
    f = [0]*(n+1)
    print(dfs(1))

9.异或序列

python turtle 蓝桥杯 蓝桥杯python组难吗_2d_09

这题是偏难了我感觉,我的能力可能最多就做到这题。

可以思考一下,异或,所有数里面在二进制中的最高位比如是11,那么如果第11位是1的只有一个,那么先手肯定赢定了吧,他只要拿这个就好了,剩下的随便怎么异或,他最高位这个都不会变。

第11位有3个呢?那就要看剩下有几个了。这样想,假设总共有4个数,那么剩下一个数最高位不是1对吧。这样先手必输。因为如果你先手拿一个最高位是1的,我拿剩下那个最高位不是1的,你只能让我异或最高位是1或者你自己异或最高位是1,如果你让我异或最高位是1那么剩下的那个我就让你异或最高位是1,这样我的最高位是1你的最高位是0, 那么如果你先把最高位是0的那个给拿掉了,我拿一个最高位是1的,剩下两个数的最高位都是1(这里的最高位是全部数的最高位),你如果异或掉我的最高位,我再拿就好了,你如果要拿最高位去异或你自己的数,那么我下次再把你异或掉,你还是输。

但是如果剩下有偶数个就不一样了,我先手拿一个最高位是1的,如果你拿剩下的最高位不是1的,我也拿一个,因为是偶数,你拿一个我肯定也能拿一个,那么最后就是会剩下偶数个最高位是1的(因为我开始拿了一个),那么就是了,你异或我我就再拿一个,你异或你自己我就异或你,到最后肯定你最高位没有1,我最高位是1

第11位有偶数个呢?那没办法那就要看最高位的下一位,就是第10位了、

if __name__ == '__main__':
    t = int(input())

    for i in range(t):
        sum = 0
        max2 = 0
        list1 = [int(x) for x in input().split()]
        for i in range(1,len(list1)):
            sum ^= list1[i]
            max2 = max(max2,list1[i])  # 顺便记录下来最大的先 
        if sum == 0 :
            print(0)
            continue
        high = 1
        while high < max2:
            high = high<<1  # 找到最大的那个的最高位是多少
        while high > 0:
            temp = 0
            for i in list1[1:]:
                if i & high != 0: # 最高位和high相同
                    temp += 1
            if temp == 1:
                print(1)
                break # 先手赢
            if temp %2 == 1: # 如果有奇数个最高位为1,那么就要看剩下的有几个
                if list1[0] % 2 == 1:  # n如果是奇数 减去奇数个最高位相同的,剩下的就是偶数个
                    print(1)
                    break # 除去最高位一样的那些,剩下的有偶数,就先手赢
                else: 
                    print(-1)
                    break # 否则后手赢
            if temp %2 == 0:  #如果最高位有偶数位,则顺延到下一位
                high = high >> 1
PS: 是感觉说的有些乱了,不会的可以和我探讨一下

10.括号序列

python turtle 蓝桥杯 蓝桥杯python组难吗_状态转移_10

这题确实是不会,搞了好久才勉勉强强搞懂。大家可以去看一下B站大雪菜的。。。我也是看他的,但有几个点我觉得我可以说一下,可能学习过程中会有些不理解

首先是状态转移方程,dp[i][j]表示的是前i个序列中,左括号比右括号多j个的方案数。

这里先考虑添加左括号,如果第i个序列上的位置是右括号,dp[i][j] = dp[i-1][j+1] + dp[i-1][j] + … + dp[i-1][0]

这是什么意思呢,就是说你在第i个序列为右括号了,那么我们可以在你的右括号前选择加左括号对吧,我如果选择加1个,你又要前i个序列左括号比右括号多j个,那么是不是前i-1个序列要左括号比右括号多j个? 因为我加了一个左括号,但第i个是右括号,这两个抵消了。如果我选择在这个右括号前面加3个左括号,那么方案数就会等于dp[i-1][j-2],想想,前面i-1个序列中,左括号比右括号多j-2个,然后又加了三个左括号一个右括号,是不是就是dp[i][j]了

如果第i个位置上是左括号那就什么都不用做了。

dp[i][j] = dp[i-1][j-1] 这里加了一个左括号,就会等于前面的i-1个序列 左括号比右括号多j-1个

然后就是我一直都想不明白的问题,为什么最后输出左括号的方案不是直接输出 dp[n][0],就是前n个序列中,左括号比右括号多0个,后面感觉是我自己想的太美满了,如果有一个序列是 (((),那么我们要添加最少的最括号能往哪里加?会好的选择就是不加左括号只加右括号,所以对于左括号来说,最后我们要选择的就是dp[4][2] ,并不是dp[4][0],只有在完美的情况下才会有dp[n][0]的出现,例如())),这样就肯定是dp[n][0],综上所述,我们输出的dp[n][i] ,i最小,一定是我们要的添加最少左括号的方案数。

右括号的话你翻个序再把左右括号互相变一下,继续用左括号的模板就好了

def func():
    dp = [[0] * (n+2) for _ in range(n+2)] # dp[i][j] 表示前i个序列,左括号比右括号多j个的方案数
    dp[0][0] = 1
    for i in range(1,n+1):
        if s[i-1] == '(':
            for j in range(1,n+1):
                dp[i][j] = dp[i-1][j-1]
        else:
            dp[i][0] = (dp[i - 1][0] + dp[i-1][1]) % mod
            for j in range(1,n+1):
                dp[i][j] = (dp[i][j-1] + dp[i-1][j+1]) % mod # 0特判是因为会出界
    for i in range(n+1):
        if dp[n][i]:  #找前n个中左括号比右括号多的数最少,表示我们添加的也最少
            return dp[n][i]
    return -1
if __name__ == '__main__':
    s = list(input())
    n = len(s)
    mod = 1e9 + 7
    left = func()
    s.reverse()  # 从左往右看是填左括号,右往左是填右括号
    # 但是因为我们func是按左括号写的,所以还需要将左括号和右括号对调一下,这样我们就不用重新写一个函数
    for i in range(n):
        if s[i] == ')':
            s[i] = '('
        else:
            s[i] = ')'
    right = func()
    print(int(left*right % mod))