2026年第十七届蓝桥杯大赛软件赛省赛 Python 大学 B 组 A-F 题 完整题解(小白友好版)

本文包含完整题目还原可直接运行的代码零基础能懂的思路讲解,适合备赛蓝桥杯的新手学习。


A 题:和平干饭日(结果填空题)

题目

铲屎官小蓝家里养了 26 只体型巨大的缅因猫。为了给它们增加生活乐趣,小蓝海淘了一台带有人工智能的 "奇葩喂食器"。这台喂食器的出粮规则极其反人类:它每天吐出的猫粮颗粒总数,是将从 1 开始的自然数依次拼接而成的整数。具体来说:

  • 第 1 天,喂食器吐出 1 颗猫粮;
  • 第 2 天,喂食器吐出 12 颗猫粮 (将 1 和 2 拼在一起);
  • 第 3 天,喂食器吐出 123 颗猫粮;
  • ......如果某天吐出的猫粮总数,能够被这 26 只猫完全平分 (即每只猫分到的颗粒数一模一样),它们就会相安无事,这一天被称为 "和平干饭日",如果不能被完全平分,它们就会为了抢夺残粮而大打出手。现在,喂食器的运行计划已经设定了整整 2026 天。请你帮忧心忡忡的小蓝算一算,在这 2026 天里,总共有多少天是 "和平干饭日"?

最终答案

77

完整代码(两种写法,推荐高效写法)

写法 1:高效模运算写法(不会溢出,速度快)
python 复制代码
count = 0
mod_val = 0  # 记录当前拼接数模26的结果
for n in range(1, 2027):
    # 计算n的位数,比如n=10是2位,n=123是3位
    digit = len(str(n))
    # 递推计算模值:之前的数左移digit位(乘10^digit)+n,再模26
    mod_val = (mod_val * (10 ** digit) + n) % 26
    # 如果模为0,就是和平干饭日
    if mod_val == 0:
        count += 1
print(count)
写法 2:暴力拼接写法(直观,Python 支持大数)
python 复制代码
count = 0
s = ""
for i in range(1, 2027):
    s += str(i)  # 拼接数字
    num = int(s)
    if num % 26 == 0:
        count += 1
print(count)

小白友好思路讲解

  1. 核心问题 :第 n 天的猫粮数是123...n这个超大数,直接计算会溢出(虽然 Python 支持大数,但效率低),所以用模运算的性质简化计算。
  2. 模运算关键公式 :(a * b + c) % m = [(a % m) * (b % m) + c % m] % m,意思是:计算过程中随时取模,结果和最后取模完全一样。
  3. 递推逻辑
    • 比如第 2 天的数12 = 1*10 + 2,模 26 的结果就是(1*10 +2) %26
    • 第 10 天的数12345678910 = 123456789 * 100 +10,模 26 的结果就是(之前的模值 * 100 +10) %26
  4. 计数:遍历 1 到 2026 天,只要模 26 等于 0,就计数 + 1,最后输出总数。

B 题:干涉条纹(结果填空题)

在国家精密光学实验室中,研究员正利用两组高功率相干激光器进行 "量子干涉条纹" 锁定实验。设 1 号激光器的输出功率为a(0≤a≤20269876543210),2 号激光器的输出功率为b(0<b≤20260123456789),其中 a,b 均为非负整数。

物理规律表明,只有当系统总功S=a+b恰好为一个完全平方数时,干涉条纹方可被成功锁定。请问,一共有多少种不同的功率配给方案 (a,b) 能够使实验成功锁定?由于方案数可能很大,你只需要给出方案数对998244353取模后的结果即可。

注意:两个方案 (a1,b1) 和 (a2,b2) 被认为是不同的,当且仅当 a1≠a2,或 b1≠b2。

最终答案

315082704(对 998244353 取模后结果)

完整代码

python 复制代码
MOD = 998244353
A_max = 20269876543210
B_max = 20260123456789
s_max = A_max + B_max  # 最大的可能的S值

import math
k_max = math.isqrt(s_max)  # 最大的k,满足k² ≤ s_max
ans = 0

for k in range(1, k_max + 1):
    s = k * k
    # 计算a的合法左边界:a ≥ s - B_max(因为b = s-a ≤ B_max)
    L = max(0, s - B_max)
    # 计算a的合法右边界:a ≤ min(A_max, s-1)(因为b = s-a >0 → a <s)
    R = min(A_max, s - 1)
    # 如果左边界≤右边界,说明这个s有合法方案,累加数量
    if L <= R:
        ans = (ans + R - L + 1) % MOD

print(ans)

小白友好思路讲解

  1. 核心问题转化 :我们要找所有满足a+b = 完全平方数s的 (a,b) 对,且 a、b 在题目给的范围内。
  2. 枚举对象:不用枚举 a 和 b(范围太大,会超时),直接枚举完全平方数的平方根 k,这样 s=k²,范围一下子缩小到 6000 多万次循环(Python 几秒就能跑完)。
  3. 合法 a 的范围计算
    • 因为 b = s - a > 0 → a < s → a 最大是 s-1,同时 a 不能超过 A_max,所以右边界 R 是两者的最小值。
    • 因为 b = s - a ≤ B_max → a ≥ s - B_max,同时 a 不能小于 0,所以左边界 L 是两者的最大值。
  4. 方案数计算 :如果 L≤R,说明这个 s 有 (R-L+1) 个合法的 a,每个 a 对应唯一的 b,累加所有合法方案数,最后取模即可。

C 题:定制展示盘(编程大题)

小蓝正在设计一款用于存放纪念币的展示盘。由于加工设备的限制,展示盘的制作必须满足以下条件:

  • 展示盘是一个矩形,由若干行、若干列的槽位组成。
  • 展示盘的行数和每行的槽位数量都至少为 2。小蓝手头共有 n 枚纪念币需要安放,他可以根据需要定制不同规格的展示盘,只要展示盘上的总槽位数量(即行数与每行槽位数的乘积)不少于 n 即可。加工费用是根据展示盘的总面积(即总槽位数量)来计算的,因此,小蓝希望在满足安放需求和设备限制的前提下,使展示盘的总槽位数量尽可能小。现在,请你帮他计算这个最小值。
输入格式

第一行包含一个正整数 T,表示数据的组数。接下来的 T 行,每行包含一个正整数 n,代表小蓝拥有的纪念币总数。

输出格式

输出共 T 行,每行包含一个整数,表示在符合所有要求的情况下,展示盘最少需要包含的槽位总数。

样例输入

plaintext

复制代码
2
3
5
样例输出

plaintext

复制代码
4
6

完整可运行代码

python 复制代码
import math
import sys
input = sys.stdin.readline  # 加速输入,处理大数据

def get_min_slot(n):
    min_total = float('inf')
    # 枚举行数a,最多枚举到sqrt(n)+10就够了,因为a*b最小的时候a和b接近sqrt(n)
    max_a = int(math.isqrt(n)) + 10
    for a in range(2, max_a + 1):
        # 计算需要的最小列数b,至少为2,且a*b≥n
        b = max(2, (n + a - 1) // a)  # (n+a-1)//a 是向上取整的写法
        total = a * b
        if total < min_total:
            min_total = total
    return min_total

# 处理输入输出
T = int(input())
for _ in range(T):
    n = int(input())
    print(get_min_slot(n))

小白友好思路讲解

  1. 核心需求 :找两个≥2 的整数 a 和 b,使得 ab≥n,且 ab 尽可能小。
  2. 枚举优化 :不用枚举所有 a 和 b,因为乘积最小的时候,a 和 b 一定接近 n 的平方根(比如 n=100,10*10=100 最小),所以只需要枚举 a 从 2 到 sqrt (n)+10 就够了,效率极高,哪怕 n 是 1e9 也能瞬间算出。
  3. 向上取整技巧 :(n + a -1) // a 是 Python 里整数向上取整的常用写法,比如 n=5,a=2,(5+2-1)//2=6//2=3,正好是需要的最小 b。
  4. 边界处理:b 必须≥2,哪怕 n 很小(比如 n=3,a=2,b=2,乘积 4)。

D 题:密室逃脱开关谜题(编程大题)

你被困在一个密室中,面前有一个控制面板,上面有 n 个开关,控制着密室中的 m 盏灯。开关编号为 0,1,...,n-1;灯编号为 0,1,...,m-1。开关与灯的作用规则如下:

  • 按下第 i 个开关 (0≤i<n),会切换第 (i mod m) 盏灯和第 (2*i mod m) 盏灯的状态。
  • 如果这两个编号相同 (即 i mod m = 2*i mod m),则只切换这一盏灯的状态。
  • 切换指:如果灯是关闭的则打开,如果灯是打开的则关闭。初始时,所有灯都处于关闭状态。你可以按任意次开关,每个开关可以被多次按下 (也可以不按)。你的目标是让所有灯最终都变为打开的状态。现在,请你计算:最少需要按多少次开关才能做到这一点;如果无论如何操作都无法让所有灯同时打开,则输出 - 1。
输入格式

第一行包含一个整数 T,表示测试数据组数。接下来 T 组数据,每组数据占一行,包含两个整数 n 和 m,分别表示开关的数量和灯的数量。

输出格式

对于每组数据,输出一行,包含一个整数,表示最少需要按开关的次数。如果无法让所有灯都打开,输出 - 1。

【样例输入】

plaintext

复制代码
2
4 4
5 5
【样例输出】

plaintext

复制代码
3
3
【样例说明】

第一组数据:有 4 个开关(编号 0-3)和 4 盏灯(编号 0-3)。开关控制关系:

  • 开关 0:控制灯 0(同一盏);
  • 开关 1:控制灯 1 和灯 2;
  • 开关 2:控制灯 2 和灯 0;
  • 开关 3:控制灯 3 和灯 2。

一种最优方案:按开关 1、开关 2、开关 3,共按 3 次。状态变化如下(关 = 0,开 = 1):

表格

状态 控制灯 灯 0 灯 1 灯 2 灯 3
初始状态 -
按开关 1 灯 1、2
按开关 2 灯 2、0
按开关 3 灯 3、2

第二组数据:有 5 个开关(编号 0-4)和 5 盏灯(编号 0-4)。开关控制关系:

  • 开关 0:控制灯 0(同一盏);
  • 开关 1:控制灯 1 和灯 2;
  • 开关 2:控制灯 2 和灯 4;
  • 开关 3:控制灯 3 和灯 1;
  • 开关 4:控制灯 4 和灯 3。

一种最优方案:按开关 0、开关 1、开关 4,共按 3 次。状态变化如下:

表格

状态 控制灯 灯 0 灯 1 灯 2 灯 3 灯 4
初始状态 -
按开关 0 灯 0
按开关 1 灯 1、2
按开关 4 灯 4、3
【评测用例规模与约定】

对于 20% 的数据:1≤t≤3,1≤n,m≤8;对于 50% 的数据:1≤t≤3,1≤n,m≤20;对于 80% 的数据:1≤t≤5,1≤n,m≤100;对于 100% 的数据:1≤t≤5,1≤n,m≤1000。

完整可运行代码

python 复制代码
import sys
from collections import deque
input = sys.stdin.readline

def solve():
    T = int(input())
    for _ in range(T):
        n, m = map(int, input().split())
        # 计算每个余数r有多少个开关:i <n 且 i ≡r mod m
        cnt = [0] * m
        for r in range(m):
            cnt[r] = (n - r + m - 1) // m  # 计算有多少个i
        
        # 构建前驱和后继
        pre = [[] for _ in range(m)]  # pre[j]是所有满足2r ≡j mod m的r
        next_j = [0] * m  # next_j[r] = 2r mod m
        for r in range(m):
            j = (2 * r) % m
            pre[j].append(r)
            next_j[r] = j
        
        # 计算每个节点的入度
        in_degree = [len(pre[j]) for j in range(m)]
        # 标记是否被访问过
        visited = [False] * m
        # 每个节点的方程右边的值,初始是1(需要奇数次翻转)
        req = [1] * m
        # 每个节点是否必须选1
        x = [0] * m
        possible = True
        ans = 0
        
        # 拓扑排序处理树部分(非环节点)
        q = deque()
        for j in range(m):
            if in_degree[j] == 0 and not visited[j]:
                q.append(j)
        
        while q:
            u = q.popleft()
            if visited[u]:
                continue
            visited[u] = True
            # 树节点的x[u]必须等于req[u]
            x_u = req[u]
            # 检查是否有开关可用
            if x_u == 1 and cnt[u] == 0:
                possible = False
            ans += x_u
            # 把x_u传递给后继节点的req
            v = next_j[u]
            req[v] ^= x_u  # 模2减法等于异或
            in_degree[v] -= 1
            if in_degree[v] == 0:
                q.append(v)
        
        # 处理环
        for j in range(m):
            if not visited[j]:
                # 找到整个环
                cycle = []
                cur = j
                while not visited[cur]:
                    visited[cur] = True
                    cycle.append(cur)
                    cur = next_j[cur]
                k = len(cycle)
                # 环的方程:x[i] ^ x[pre[i]] = req[i]
                # 枚举环的第一个节点的x值,0或1,选总次数最小的
                min_cycle = float('inf')
                for x0 in [0, 1]:
                    # 检查x0是否合法
                    if x0 == 1 and cnt[cycle[0]] == 0:
                        continue
                    current_x = x0
                    total = current_x
                    valid = True
                    temp_req = req[cycle[0]] ^ current_x
                    # 遍历环的其他节点
                    for i in range(1, k):
                        node = cycle[i]
                        xi = temp_req
                        if xi == 1 and cnt[node] == 0:
                            valid = False
                            break
                        total += xi
                        temp_req = req[node] ^ xi
                    # 检查环的闭合条件
                    if (temp_req ^ x0) == req[cycle[-1]] and valid:
                        if total < min_cycle:
                            min_cycle = total
                if min_cycle == float('inf'):
                    possible = False
                else:
                    ans += min_cycle
        
        print(ans if possible else -1)

if __name__ == "__main__":
    solve()

小白友好思路讲解

  1. 关键简化 1:每个开关按 2 次等于没按,所以每个开关要么按 0 次,要么按 1 次(按 1 次是最小次数)。
  2. 关键简化 2 :所有 i ≡ r mod m 的开关,按下的效果完全一样(都是翻转 r 和 2r mod m 的灯),所以我们只需要考虑每个余数 r 选不选(选 1 个,次数 + 1)。
  3. 问题转化:我们需要选一组 r,使得每盏灯被翻转奇数次(从关到开),且选的 r 数量最少。
  4. 图结构分析 :每个节点 r 的后继是 2r mod m,整个图是由环 + 指向环的树 组成的。
    • 树部分:用拓扑排序从叶子节点开始处理,每个节点的选择是固定的,直接计算即可。
    • 环部分:环的第一个节点有两种选择(0 或 1),分别计算两种选择的总次数,选最小的那个,如果都不合法,就输出 - 1。
  5. 合法性检查:如果某个 r 必须选 1,但没有对应的开关(cnt [r]=0),就无解。

E 题:奇偶博弈(编程大题)

小蓝和小桥正在玩一个基于数列的博弈游戏。初始时,给定一个长度为 N 的数列 W1,W2,...,WN,数列中的每一个元素均为正奇数。游戏由小蓝先手,两人交替进行操作。在每次操作中,当前操作者需要选择数列中一个严格大于 0 的元素 Wi,并将其替换为一个严格小于它的非负整数 W′i(即 0≤W′i<Wi)。该替换操作必须严格满足以下奇偶性限制:

  1. 若选定的 Wi 为奇数,则必须将其替换为 Wi−1(也就是只能减 1,变成偶数)。
  2. 若选定的 Wi 为偶数,则替换后的新数 W′i 也必须是一个偶数。当轮到某一方操作时,若其无法进行任何合法的替换,则该方输掉游戏,另一方获胜。假设小蓝和小桥都绝顶聪明,均采取最优策略,请问最终谁将赢得这场游戏?
输入格式

输入的第一行包含一个整数 T,表示测试用例的组数。接下来依次输入 T 组测试用例。对于每组测试用例:

  • 第一行包含一个整数 N,表示数列的长度。
  • 第二行包含 N 个正奇数 W1,W2,...,WN,相邻两个数字之间用空格隔开。
输出格式

对于每组测试用例,输出一行结果。如果小蓝获胜,输出 L;如果小桥获胜,输出 Q。

样例输入

plaintext

复制代码
2
2
5 1
2
1 1
样例输出

plaintext

复制代码
L
Q

完整可运行代码

python 复制代码
import sys
input = sys.stdin.readline

def main():
    T = int(input())
    for _ in range(T):
        N = int(input())
        W = list(map(int, input().split()))
        # 数数列里1的个数
        count_1 = W.count(1)
        # 1的个数是奇数,小蓝赢;偶数,小桥赢
        if count_1 % 2 == 1:
            print("L")
        else:
            print("Q")

if __name__ == "__main__":
    main()

小白友好思路讲解

  1. 核心规则拆解
    • 初始所有数都是奇数,所以第一次操作只能把奇数减 1 变成偶数。
    • 偶数只能换成更小的偶数,直到换成 0(0 是偶数,不能再操作了)。
  2. 关键结论 1 :大于 1 的奇数,对游戏胜负没有影响!
    • 比如数 5:先手把 5 变成 4,后手可以把 4 变成 2,先手再把 2 变成 0,等于这个数用了 3 步,但是步数的奇偶性是固定的?不对,更简单的:大于 1 的奇数,操作者可以完全控制它的步数奇偶性,所以不会影响最终胜负,只有 1 的个数会影响。
  3. 关键结论 2 :只有数字 1 会决定胜负!
    • 数字 1 是奇数,只能换成 0,一步就结束,没有操作空间。
    • 1 的个数是奇数:先手小蓝会拿到最后一个 1,赢。
    • 1 的个数是偶数:后手小桥会拿到最后一个 1,赢。
  4. 样例验证
    • 第一组样例:5 和 1,1 的个数是 1(奇数),小蓝赢,输出 L。
    • 第二组样例:1 和 1,1 的个数是 2(偶数),小桥赢,输出 Q。

F 题:蓝小圈(编程大题)

有 N 个用户,初始时每个用户都是独立的社交圈,每个用户的初始活跃度为 0。现在有 Q 条操作指令,指令分为 4 种:

  1. 1 X Y:执行 "建立好友",连通用户 X 与 Y 的社交圈。
  2. 2 X A:执行 "个人动态",为用户 X 的活跃度增加 A。
  3. 3 X A:执行 "圈层热议",为 X 所在社交圈内的所有用户活跃度增加 A。
  4. 4 X:执行 "数据查询",查询用户 X 的当前活跃度。
输入格式

第一行包含两个整数 N 和 Q,分别表示用户数量和操作指令数量。接下来 Q 行,每行是一条操作指令,格式如上述。

输出格式

针对每一条 "数据查询" 指令,输出单独的一行整数,代表目标用户当前的活跃度数值。

样例输入

plaintext

复制代码
3 6
1 1 2
1 1 3
2 1 5
3 2 5
4 1
4 2
样例输出

plaintext

复制代码
10
5

完整可运行代码(带懒标记的并查集,小白友好版)

python 复制代码
import sys
sys.setrecursionlimit(1 << 25)
input = sys.stdin.readline

class UnionFind:
    def __init__(self, size):
        self.fa = list(range(size + 1))  # 用户编号从1开始
        self.val = [0] * (size + 1)       # val[x]:x到父节点的活跃度增量
        self.tag = [0] * (size + 1)       # tag[root]:整个集合的整体增量,只有根节点有效
    
    # 找根节点+路径压缩
    def find(self, x):
        if self.fa[x] != x:
            orig_fa = self.fa[x]
            self.fa[x] = self.find(self.fa[x])  # 父节点直接指向根
            self.val[x] += self.val[orig_fa]    # 更新x到根的增量
        return self.fa[x]
    
    # 合并x和y的社交圈
    def union(self, x, y):
        root_x = self.find(x)
        root_y = self.find(y)
        if root_x == root_y:
            return
        # 把root_y合并到root_x下面
        self.fa[root_y] = root_x
        # 调整root_y的val,保证合并后活跃度不变
        self.val[root_y] = self.tag[root_y] - self.tag[root_x]
    
    # 给x个人加A活跃度
    def add_personal(self, x, A):
        self.find(x)  # 先路径压缩,保证x的父节点是根
        self.val[x] += A
    
    # 给x所在的整个集合加A活跃度
    def add_group(self, x, A):
        root = self.find(x)
        self.tag[root] += A
    
    # 查询x的当前活跃度
    def query(self, x):
        root = self.find(x)
        return self.val[x] + self.tag[root]

# 主程序
def main():
    N, Q = map(int, input().split())
    uf = UnionFind(N)
    for _ in range(Q):
        parts = list(map(int, input().split()))
        op = parts[0]
        if op == 1:
            X, Y = parts[1], parts[2]
            uf.union(X, Y)
        elif op == 2:
            X, A = parts[1], parts[2]
            uf.add_personal(X, A)
        elif op == 3:
            X, A = parts[1], parts[2]
            uf.add_group(X, A)
        elif op == 4:
            X = parts[1]
            print(uf.query(X))

if __name__ == "__main__":
    main()

小白友好思路讲解

  1. 核心问题 :需要快速合并集合、给整个集合加值、给单个元素加值、查询单个元素的值,暴力遍历会超时(2e5 次操作),所以用带懒标记的并查集
  2. 并查集核心作用:快速合并两个集合,快速找到元素所在的集合根节点,时间复杂度几乎是 O (1)。
  3. 懒标记的作用:给整个集合加值的时候,不用遍历所有元素,只需要给集合的根节点加一个标记 tag,代表整个集合都要加这个值,查询的时候再算进去,效率极高。
  4. 路径压缩:找根节点的时候,把元素的父节点直接指向根,同时更新元素到根的增量,下次查询更快,也保证了数值的正确性。
  5. 合并处理:合并两个集合的时候,调整被合并的根节点的 val 值,保证合并前后所有元素的活跃度不变,不会出现数值错误。
  6. 样例验证
    • 合并 1、2、3,根是 1。
    • 给 1 加 5:val [1] +=5 → 1 的活跃度是 5+0=5。
    • 给 2 所在的集合加 5:根是 1,tag [1] +=5 → tag [1]=5。
    • 查询 1:val [1]+tag [1] =5+5=10。
    • 查询 2:val [2]+tag [1] =0+5=5。和样例输出完全一致。
相关推荐
khalil10202 小时前
代码随想录算法训练营Day-46 动态规划13 | 647. 回文子串、516.最长回文子序列、动态规划总结
数据结构·c++·算法·leetcode·动态规划·回文子串·回文子序列
2301_781571422 小时前
JavaScript中Object-getOwnPropertySymbols获取方法
jvm·数据库·python
学习3人组2 小时前
柔性排产时序算法+中间过程+阶段目标 细化表格
算法·mes
he___H2 小时前
算法快与慢--哈希+双指针
算法·leetcode·哈希算法
呃呃本2 小时前
算法题(回溯)
算法
刀法如飞2 小时前
Rust数组去重的20种实现方式,AI时代用不同思路解决问题
人工智能·算法·ai编程
yxc_inspire2 小时前
25年CCPC福建邀请赛补题
学习·算法
计算机魔术师2 小时前
【技术趣闻 | AI Agent Skill】为什么 AI 总绕着同一个脑回路转?多语言思维采样:让 Agent 从“给一个答案”变成“给一组方案”
开源
Raink老师2 小时前
用100道题拿下你的算法面试(链表篇-4):合并 K 个有序链表
算法·链表·面试
倒霉熊dd3 小时前
Python学习(第一部分 语法与数据结构/核心基础)
大数据·python·学习·pip