第十二届蓝桥杯国赛真题+题解 Python

文章目录

    • 写在前面
    • 试题A:带宽
    • 试题 B: 纯质数
    • 试题C: 完全日期
    • 试题D: 最小权值
    • 试题 E: 大写
    • 试题 F: 123
    • 试题 G: 冰山
    • 试题 H: 和与乘积
    • 试题 I: 二进制问题
    • 试题 J: 翻转括号序列
    • 完整真题

写在前面

本次蓝桥杯国赛的题目,感觉质量比省赛时好了不少,题目难易适中,有难有易。不得不吐槽一下早上的打车经历,到考场10分钟左右的车程,愣是等车等了20分钟。

比赛超常发挥,我没想过我能把10道题全做出来。把自己的解题思路写在这里,也仅代表我个人理解得出的思路,不代表官方答案,欢迎大家交流,指正错误。

题目不太完整,没找到Python A组的完整试题,就从CA和CB里找了些一样的题。后续再补充。少两个题目,只记得一个是二进制计 1 1 1,另一个就不记得了。


拿到全部题目了,更新一下。——2021.6.7


试题A:带宽

本题总分:5分

【问题描述】

小蓝家的网络带宽是 200 M b p s 200 Mbps 200Mbps,请问,使用小蓝家的网络理论上每秒钟最 多可以从网上下载多少 M B MB MB 的内容。

【思路分析】

这个题就比较简单了,签到题,Mbps/8就是实际速度

【参考答案】

25

试题 B: 纯质数

本题总分:5 分

【问题描述】

如果一个正整数只有 1 1 1 和它本身两个约数,则称为一个质数(又称素数)。

前几个质数是: 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 , 31 , 37 , . . . 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, ... 2,3,5,7,11,13,17,19,23,29,31,37,...

如果一个质数的所有十进制数位都是质数,我们称它为纯质数。例如: 2 , 3 , 5 , 7 , 23 , 37 2, 3, 5, 7, 23, 37 2,3,5,7,23,37 都是纯质数,而 11 , 13 , 17 , 19 , 29 , 31 11, 13, 17, 19, 29, 31 11,13,17,19,29,31 不是纯质数。当然 1 , 4 , 35 1, 4, 35 1,4,35 也不是纯质数。

请问,在 1 1 1 20210605 20210605 20210605 中,有多少个纯质数?

【思路分析】

这个题比较直观,首先判断[1、4、6、8、9、0]在这个数里边,就一定不是纯质数。

def isPurePrime(n):
    s = str(n)
    for i in range(len(s)):
        if s[i] == '0' or s[i] == '1' or s[i] == '4' or s[i] == '6' or s[i] == '8' or s[i] == '9':
            return False
    if isPrime(n):
        return True
    return False

然后如果都不在,再判断这个数是不是质数,例如22

def isPrime(n):
    if n <= 3:
        return n >= 2
    if (n + 1) % 6 != 0 and (n - 1) % 6 != 0:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

最后循环跑一下结果就出来了

【参考答案】

1903

比赛时把0忘了,心里默念 m m p mmp mmp


试题C: 完全日期

本题总分:10 分

【问题描述】

如果一个日期中年月日的各位数字之和是完全平方数,则称为一个完全日期。

例如: 2021 2021 2021 6 6 6 5 5 5 日的各位数字之和为 2 + 0 + 2 + 1 + 6 + 5 = 16 2 + 0 + 2 + 1 + 6 + 5 = 16 2+0+2+1+6+5=16 ,而 16 16 16 是一个完全平方数,它是 4 4 4 的平方。所以 2021 2021 2021 6 6 6 5 5 5 日是一个完全日期。

例如: 2021 2021 2021 6 6 6 23 23 23 日的各位数字之和为 2 + 0 + 2 + 1 + 6 + 2 + 3 = 16 2 + 0 + 2 + 1 + 6 + 2 + 3 = 16 2+0+2+1+6+2+3=16,是一个完全平方数。所以 2021 2021 2021 6 6 6 23 23 23 日也是一个完全日期。

请问,从 2001 2001 2001 1 1 1 1 1 1 日到 2021 2021 2021 12 12 12 31 31 31 日中,一共有多少个完全日期?

【思路分析】

常规日期题,模拟日期操作然后判断即可。日期,最大 8 8 8 位数,且最大的和为2+0+1+9+1+2+3+1=19这里经过大佬指正,最大的和应为 2 + 0 + 1 + 9 + 1 + 2 + 2 + 9 = 26 2+0+1+9+1+2+2+9=26 2+0+1+9+1+2+2+9=26,所以可以提前算出来[1,4,9,16,25]这几个数,然后直接判断即可,不必重复计算。

def wonderDate(year, month, day):
    date = [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
    year = list(map(int, list(str(year))))
    month = list(map(int, list(str(month))))
    day = list(map(int, list(str(day))))
    return sum(year) + sum(month) + sum(day) in date


year, month, day, c = 2001, 1, 1, 0
while year < 2022:
    if wonderDate(year, month, day):
        c += 1
    day += 1
    if year in [2004, 2008, 2012, 2016, 2020] and month == 2 and day > 29:
        day = 1
        month += 1
    elif month == 2 and day > 28:
        day = 1
        month += 1
    elif month in [1, 3, 5, 7, 8, 10, 12] and day > 31:
        day = 1
        month += 1
    elif month in [4, 6, 9, 11] and day > 30:
        day = 1
        month += 1
    if month > 12:
        month = 1
        year += 1
print(c)

【参考答案】

977

试题D: 最小权值

本题总分:10 分

【问题描述】

对于一棵有根二叉树 T T T,小蓝定义这棵树中结点的权值 W ( T ) W(T) W(T) 如下:

空子树的权值为 0 0 0

如果一个结点 v v v 有左子树 L L L, 右子树 R R R,分别有 C ( L ) C(L) C(L) C ( R ) C(R) C(R) 个结点,则

W ( v ) = 1 + 2 W ( L ) + 3 W ( R ) + ( C ( L ) ) 2 C ( R ) 。 W(v) = 1 + 2W(L) + 3W(R) + (C(L))^2 C(R)。 W(v)=1+2W(L)+3W(R)+(C(L))2C(R)

树的权值定义为树的根结点的权值。

小蓝想知道,对于一棵有 2021 2021 2021 个结点的二叉树,树的权值最小可能是多 少?

【问题分析】

这个题不太会做,蒙了一下,觉得应该是一棵完全二叉树,并且多余的部分肯定是在右侧,因为左边的子节点数量是平方。

然后开始模拟。

nodes = [[0],
        [0,0],
        [0,0,0,0]]  # 这是模拟那棵2021个节点的树,太占地方就不放完了
for i in range(1024 - 998 + 1, len(nodes[9])):  # 初始化底层状态
    nodes[9][i] = 1
for i in range(8, -1, -1):
    for j in range(len(nodes[i])):
        # 根据公式计算,其中完全二叉树左下节点数量和右下节点数量 = 2**子节点层数-1
        nodes[i][j] = 1 + 2 * nodes[i + 1][j * 2] + 3 * nodes[i + 1][j * 2 + 1] + ((2 ** (10 - i) - 1) ** 2) * (2 ** (10 - i) - 1)
for i in nodes:
    print(i)
print(nodes[0][0])

写到这儿,又发现一个问题,这棵树左下节点不能直接按照 2 n − 1 − 1 2^{n-1}-1 2n11 来算,又错了,,答案就不放了,这个思路可供也仅供参考。


试题 E: 大写

时间限制: 1.0s 内存限制: 256.0MB 本题总分:15 分

【问题描述】

给定一个只包含大写字母和小写字母的字符串,请将其中所有的小写字母 转换成大写字母后将字符串输出。

【输入格式】

输入一行包含一个字符串。

【输出格式】

输出转换成大写后的字符串。

【样例输入 1】

 LanQiao

【样例输出 1】

 LANQIAO

【思路分析】

签到题

【代码】

print(input().upper())

试题 F: 123

时间限制: 1.0s 内存限制: 256.0MB 本题总分:15 分

【问题描述】

小蓝发现了一个有趣的数列,这个数列的前几项如下:

1 , 1 , 2 , 1 , 2 , 3 , 1 , 2 , 3 , 4 , . . 1, 1, 2, 1, 2, 3, 1, 2, 3, 4, .. 1,1,2,1,2,3,1,2,3,4,..

小蓝发现,这个数列前 1 1 1 项是整数 1 1 1,接下来 2 2 2 项是整数 1 1 1 2 2 2,接下来 3 3 3 项是整数 1 1 1 3 3 3,接下来 4 4 4 项是整数 1 1 1 4 4 4,依次类推。

小蓝想知道,这个数列中,连续一段的和是多少。

【输入格式】

输入的第一行包含一个整数 T T T,表示询问的个数。

接下来 T T T 行,每行包含一组询问,其中第 i i i 行包含两个整数 l i l_i li r i r_i ri,表示 询问数列中第 l i l_i li 个数到第 r i r_i ri 个数的和。

【输出格式】

输出 T T T 行,每行包含一个整数表示对应询问的答案。

【样例输入】

3
1 1
1 3
5 8

【样例输出】

1
4
8

【思路分析】

这个题我没找到啥太好的规律,就只能通过模拟的方式模拟数组,然后出区间和,通过判断最大值,降低一定的时间。估计超时不能拿满分。

【代码实现】

n = int(input())
l, r = [], []
for i in range(n):
    s = input().split()
    l.append(int(s[0]))
    r.append(int(s[1]))
m = max(r)
m_c = m + 0
row = 1
while m_c > 0:
    for i in range(row):
        m_c -= 1
    row += 1
row -= 1
ls = []
for i in range(1, row + 1):
    for j in range(1, i + 1):
        ls.append(j)
for i in range(n):
    print(sum(ls[l[i] - 1:r[i]]))

试题 G: 冰山

时间限制: 1.0s 内存限制: 256.0MB 本题总分:20 分

【问题描述】

一片海域上有一些冰山,第 i i i 座冰山的体积为 V i V_i Vi

随着气温的变化,冰山的体积可能增大或缩小。第 i i i 天,每座冰山的变化 量都是 X i X_i Xi。当 X i > 0 X_i > 0 Xi>0 时,所有冰山体积增加 X i X_i Xi;当 X i < 0 X_i < 0 Xi<0 时,所有冰山体积减 少 − X i −X_i Xi;当 X i = 0 X_i = 0 Xi=0 时,所有冰山体积不变。

如果第 i i i 天某座冰山的体积变化后小于等于 0 0 0,则冰山会永远消失。

冰山有大小限制 k k k。如果第 i i i 天某座冰山 j j j 的体积变化后 V j V_j Vj 大于 k k k,则它 会分裂成一个体积为 k k k 的冰山和 V j − k V_j − k Vjk 座体积为 1 1 1 的冰山。

i i i 天结束前(冰山增大、缩小、消失、分裂完成后),会漂来一座体积为 Y i Y_i Yi 的冰山( Y i = 0 Y_i = 0 Yi=0 表示没有冰山漂来)。

小蓝在连续的 m m m 天对这片海域进行了观察,并准确记录了冰山的变化。小 蓝想知道,每天结束时所有冰山的体积之和(包括新漂来的)是多少。

由于答案可能很大,请输出答案除以 998244353 998244353 998244353 的余数。

【输入格式】

输入的第一行包含三个整数 n , m , k n, m, k n,m,k分别表示初始时冰山的数量、观察的 天数以及冰山的大小限制。

第二行包含 n n n 个整数 V 1 , V 2 , . . . , V n V_1, V_2, ... , V_n V1,V2,...,Vn,表示初始时每座冰山的体积。

接下来 m m m 行描述观察的 m m m 天的冰山变化。其中第 i i i 行包含两个整数 X i , Y i X_i , Y_i Xi,Yi, 意义如前所述。

【输出格式】

输出 m m m 行,每行包含一个整数,分别对应每天结束时所有冰山的体积之和 除以 998244353 998244353 998244353 的余数。

【样例输入】

1 3 6
1
6 1
2 2
-1 1

【样例输出】

8
16
11

【样例说明】

在本样例说明中,用 [ a 1 , a 2 , . . . , a n ] [a_1, a_2, ... , a_n] [a1,a2,...,an] 来表示每座冰山的体积。

初始时的冰山为 [ 1 ] [1] [1]

1 1 1 天结束时,有 3 3 3 座冰山: [ 1 , 1 , 6 ] [1, 1, 6] [1,1,6]

2 2 2 天结束时,有 6 6 6 座冰山: [ 1 , 1 , 2 , 3 , 3 , 6 ] [1, 1, 2, 3, 3, 6] [1,1,2,3,3,6]

3 3 3 天结束时,有 5 5 5 座冰山: [ 1 , 1 , 2 , 2 , 5 ] [1, 1, 2, 2, 5] [1,1,2,2,5]

【思路分析】

这个题虽然看起来很长,但仔细读其实也不难,跟着步骤一步步去模拟就行了。但要注意避坑:

分裂的话,比如上限是6,有个冰山是8,不是分裂成[6,2],而是分解成[6,1,1]。最后记得%998244353

这个题的数据量也比较大,可能跑不完。

【代码实现】

n, m, k = map(int, input().split())  # 冰山数量、观察天数、大小限制
ices = list(map(int, input().split()))  # 每座冰山
for i in range(m):
    x, y = map(int, input().split())  # X:冰山每天的变化量  Y: 结束时飘来的冰山
    for i in range(len(ices)):
        ices[i] += x
    if x > 0:  # 增加,要考虑分裂
        for i in range(len(ices)):
            if ices[i] > k:
                for j in range(ices[i] - k):
                    ices.append(1)
                ices[i] = k
    else:  # 减少,考虑消失
        ices = list(filter(lambda x: x > 0, ices))
    ices.append(y)  # 飘来一座冰山
    print(sum(ices) % 998244353)

试题 H: 和与乘积

时间限制: 1.0s 内存限制: 512.0MB 本题总分:20 分

【问题描述】

给定一个数列 A = ( a 1 , a 2 , . . . , a n ) A = (a_1, a_2, ... , a_n) A=(a1,a2,...,an),问有多少个区间 [ L , R ] [L, R] [L,R] 满足区间内元素 的乘积等于他们的和,即 a L ∗ a L + 1 . . . a R = a L + a L + 1 + . . . + a R a_L * a_{L+1} ... a_R = a_L + a_{L+1} + ... + a_R aLaL+1...aR=aL+aL+1+...+aR

【输入格式】

输入第一行包含一个整数 n n n,表示数列的长度。

第二行包含 n 个整数,依次表示数列中的数 a 1 , a 2 , . . . , a n a_1, a_2, ... , a_n a1,a2,...,an

【输出格式】

输出仅一行,包含一个整数表示满足如上条件的区间的个数。

【样例输入】

4
1 3 2 2

【样例输出】

6

【样例解释】

符合条件的区间为 [ 1 , 1 ] , [ 1 , 3 ] , [ 2 , 2 ] , [ 3 , 3 ] , [ 3 , 4 ] , [ 4 , 4 ] [1, 1], [1, 3], [2, 2], [3, 3], [3, 4], [4, 4] [1,1],[1,3],[2,2],[3,3],[3,4],[4,4]

【思路分析】

这个题,两层for循环遍历一下就出来了,就是不知道能不能跑通所有测试点。。。

【代码实现】

n = int(input())
ls = list(map(int, input().split()))
c = 0
for i in range(n):
    s = 0
    cj = 1
    for j in range(n - i - 1, n):
        s += ls[j]
        cj *= ls[j]
        if s == cj:
            c += 1
print(c)

试题 I: 二进制问题

时间限制: 1.0s 内存限制: 512.0MB 本题总分:25 分

【问题描述】

小蓝最近在学习二进制。他想知道 1 1 1 N N N 中有多少个数满足其二进制表示 中恰好有 K K K 1 1 1。你能帮助他吗?

【输入格式】

输入一行包含两个整数 N N N K K K

【输出格式】

输出一个整数表示答案。

【样例输入】

7 2

【样例输出】

3

【思路分析】

这个题乍一看也比较简单哈,第一遍做的时候秒出。

n, k = map(int, input().split())
c = 0
for i in range(1, n + 1):
    if bin(i).count('1') == k:
        c += 1
print(c)

后来做完全部的题还有1个多小时,我就开始检查,检查到这,想着好歹是个25分的题,不应该这么简单啊。然后看了下测试样例

  1. 对于 30 % 30\% 30% 的评测用例, 1 ≤ N ≤ 106 1 ≤ N ≤ 106 1N106 , 1 ≤ K ≤ 10 1 ≤ K ≤ 10 1K10
  2. 对于 60 % 60\% 60% 的评测用例, 1 ≤ N ≤ 2 × 1 0 9 , 1 ≤ K ≤ 30 1 ≤ N ≤ 2 × 10^9 , 1 ≤ K ≤ 30 1N2×109,1K30
  3. 对于所有评测用例, 1 ≤ N ≤ 1 0 18 , 1 ≤ K ≤ 50 1 ≤ N ≤ 10^{18} , 1 ≤ K ≤ 50 1N1018,1K50

仅仅到 2 ∗ 1 0 7 2*10^7 2107就很费劲了,1秒之内肯定是跑不完的,也就是按原程序估计最多拿40%的分数。于是在想办法优化这个算法。

  1. 首先,k最大二进制的长度,就不用算了,肯定是 0 0 0,比如,7111,但如果k=4,那肯定就不用算了呀

  2. 其次,由于这是找有几个,跟大小并没有关系,例如从7里边找217111,找两个,那就是数学上的组合问题, C 3 2 C_3^2 C32

    C 5 3 = ( 5 ∗ 4 ∗ 3 ) / ( 3 ∗ 2 ∗ 1 ) C_5^3=(5*4*3)/(3*2*1) C53=(543)/(321)

  3. 对于不是 2 n − 1 2^n-1 2n1的数字,拿12举例,12=0b1100,这时如果统计3位的话,那么肯定会出错的,我们可以通过len(bin(12)[2:])-1的方式得到3,即低一位111的情况,

  4. 然后就可以使用 C 3 3 C_3^3 C33的方式,求到1

  5. 然后剩下的,再从8开始循环,到12结束。

  6. 如此一来,可节约一定的时间。但还是没办法跑通最大的测试点。能多跑些分是些分吧。

【代码实现】

import functools


@functools.lru_cache()
def C(down, up):
    fz, fm = 1, 1
    for i in range(1, up + 1):
        fm *= down
        down -= 1
        fz *= i
    return fm // fz


n, k = map(int, input().split())
if k > len(bin(n)[2:]):
    print(0)
    exit()
l = len(bin(n)[2:]) - 1
c = C(l, k)
new_b = int('1' + '0' * l, 2)
for i in range(new_b, n + 1):
    if bin(i).count('1') == k:
        c += 1
print(c)

被自己蠢到了,考试的时候没想着直接写一个C方法,而是写了阶乘,然后用阶乘乘除得到Cxx,白白浪费时间。。。


试题 J: 翻转括号序列

时间限制: 2.0s 内存限制: 512.0MB 本题总分:25 分

【问题描述】

给定一个长度为 n n n 的括号序列,要求支持两种操作:

  1. [ L i , R i ] [L_i , R_i ] [Li,Ri] 区间内(序列中的第 L i L_i Li 个字符到第 R i R_i Ri 个字符)的括号全部翻 转(左括号变成右括号,右括号变成左括号)。
  2. 求出以 L i L_i Li 为左端点时,最长的合法括号序列对应的 R i R_i Ri (即找出最大的 R i R_i Ri 使 [ L i , R i ] [L_i , R_i ] [Li,Ri] 是一个合法括号序列)。

【输入格式】

输入的第一行包含两个整数 n , m n, m n,m,分别表示括号序列长度和操作次数。

第二行包含给定的括号序列,括号序列中只包含左括号和右括号。

接下来 m m m 行,每行描述一个操作。如果该行为 “ 1 1 1 L i L_i Li R i R_i Ri”,表示第一种操作, 区间为 [ L i , R i ] [L_i , R_i ] [Li,Ri] ;如果该行为 “ 2 2 2 L i L_i Li” 表示第二种操作,左端点为 L i L_i Li

【输出格式】

对于每个第二种操作,输出一行,表示对应的 R i R_i Ri。如果不存在这样的 R i R_i Ri, 请输出 0 0 0

【样例输入】

7 5
((())()
2 3
2 2
1 3 5
2 3
2 1

【样例输出】

4
7
0
0

【思路分析】

这个题跟冰山题一样,乍一看比较复杂,但其实也不难,一步步走就行。

这里也要注意避个坑:序列内的括号全部翻转。。这个我刚开始理解错了,不是顺序翻转,而是())(

判断是否有完整的括号序列,可以用这个方式实现,逐个删除完整括号,最后判断长度即可:

while '()' in s:
    s = s.replace('()', '')
if len(s) == 0:

【代码实现】

n, m = map(int, input().split())
kh = list(input())
for i in range(m):
    o = input().split()
    if o[0] == '1':
        l = int(o[1])
        r = int(o[2])
        for i in range(l - 1, r):
            if kh[i] == '(':
                kh[i] = ')'
            else:
                kh[i] = '('
    else:
        l = int(o[1])
        r = n + 0
        while r > l:
            s = ''.join(kh[l - 1:r])
            while '()' in s:
                s = s.replace('()', '')
            if len(s) == 0:
                break
            r -= 1
        if r > l:
            print(r)
        else:
            print(0)

完整真题

完整题目及代码网盘自取:

https://pan.baidu.com/s/198t23JyVNftFSZzYyvYqKg

提取码:yoyo

你可能感兴趣的:(笔记,日常学习随笔,算法_蓝桥杯,python,算法,2021蓝桥杯国赛,数据结构)