本文包含完整题目还原 、可直接运行的代码 、零基础能懂的思路讲解,适合备赛蓝桥杯的新手学习。
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)
小白友好思路讲解
- 核心问题 :第 n 天的猫粮数是
123...n这个超大数,直接计算会溢出(虽然 Python 支持大数,但效率低),所以用模运算的性质简化计算。 - 模运算关键公式 :(a * b + c) % m = [(a % m) * (b % m) + c % m] % m,意思是:计算过程中随时取模,结果和最后取模完全一样。
- 递推逻辑 :
- 比如第 2 天的数
12 = 1*10 + 2,模 26 的结果就是(1*10 +2) %26 - 第 10 天的数
12345678910 = 123456789 * 100 +10,模 26 的结果就是(之前的模值 * 100 +10) %26
- 比如第 2 天的数
- 计数:遍历 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)
小白友好思路讲解
- 核心问题转化 :我们要找所有满足a+b = 完全平方数s的 (a,b) 对,且 a、b 在题目给的范围内。
- 枚举对象:不用枚举 a 和 b(范围太大,会超时),直接枚举完全平方数的平方根 k,这样 s=k²,范围一下子缩小到 6000 多万次循环(Python 几秒就能跑完)。
- 合法 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 是两者的最大值。
- 方案数计算 :如果 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))
小白友好思路讲解
- 核心需求 :找两个≥2 的整数 a 和 b,使得 ab≥n,且 ab 尽可能小。
- 枚举优化 :不用枚举所有 a 和 b,因为乘积最小的时候,a 和 b 一定接近 n 的平方根(比如 n=100,10*10=100 最小),所以只需要枚举 a 从 2 到 sqrt (n)+10 就够了,效率极高,哪怕 n 是 1e9 也能瞬间算出。
- 向上取整技巧 :(n + a -1) // a 是 Python 里整数向上取整的常用写法,比如 n=5,a=2,(5+2-1)//2=6//2=3,正好是需要的最小 b。
- 边界处理: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:每个开关按 2 次等于没按,所以每个开关要么按 0 次,要么按 1 次(按 1 次是最小次数)。
- 关键简化 2 :所有 i ≡ r mod m 的开关,按下的效果完全一样(都是翻转 r 和 2r mod m 的灯),所以我们只需要考虑每个余数 r 选不选(选 1 个,次数 + 1)。
- 问题转化:我们需要选一组 r,使得每盏灯被翻转奇数次(从关到开),且选的 r 数量最少。
- 图结构分析 :每个节点 r 的后继是 2r mod m,整个图是由环 + 指向环的树 组成的。
- 树部分:用拓扑排序从叶子节点开始处理,每个节点的选择是固定的,直接计算即可。
- 环部分:环的第一个节点有两种选择(0 或 1),分别计算两种选择的总次数,选最小的那个,如果都不合法,就输出 - 1。
- 合法性检查:如果某个 r 必须选 1,但没有对应的开关(cnt [r]=0),就无解。
E 题:奇偶博弈(编程大题)
小蓝和小桥正在玩一个基于数列的博弈游戏。初始时,给定一个长度为 N 的数列 W1,W2,...,WN,数列中的每一个元素均为正奇数。游戏由小蓝先手,两人交替进行操作。在每次操作中,当前操作者需要选择数列中一个严格大于 0 的元素 Wi,并将其替换为一个严格小于它的非负整数 W′i(即 0≤W′i<Wi)。该替换操作必须严格满足以下奇偶性限制:
- 若选定的 Wi 为奇数,则必须将其替换为 Wi−1(也就是只能减 1,变成偶数)。
- 若选定的 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 变成偶数。
- 偶数只能换成更小的偶数,直到换成 0(0 是偶数,不能再操作了)。
- 关键结论 1 :大于 1 的奇数,对游戏胜负没有影响!
- 比如数 5:先手把 5 变成 4,后手可以把 4 变成 2,先手再把 2 变成 0,等于这个数用了 3 步,但是步数的奇偶性是固定的?不对,更简单的:大于 1 的奇数,操作者可以完全控制它的步数奇偶性,所以不会影响最终胜负,只有 1 的个数会影响。
- 关键结论 2 :只有数字 1 会决定胜负!
- 数字 1 是奇数,只能换成 0,一步就结束,没有操作空间。
- 1 的个数是奇数:先手小蓝会拿到最后一个 1,赢。
- 1 的个数是偶数:后手小桥会拿到最后一个 1,赢。
- 样例验证 :
- 第一组样例:5 和 1,1 的个数是 1(奇数),小蓝赢,输出 L。
- 第二组样例:1 和 1,1 的个数是 2(偶数),小桥赢,输出 Q。
F 题:蓝小圈(编程大题)
有 N 个用户,初始时每个用户都是独立的社交圈,每个用户的初始活跃度为 0。现在有 Q 条操作指令,指令分为 4 种:
1 X Y:执行 "建立好友",连通用户 X 与 Y 的社交圈。2 X A:执行 "个人动态",为用户 X 的活跃度增加 A。3 X A:执行 "圈层热议",为 X 所在社交圈内的所有用户活跃度增加 A。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()
小白友好思路讲解
- 核心问题 :需要快速合并集合、给整个集合加值、给单个元素加值、查询单个元素的值,暴力遍历会超时(2e5 次操作),所以用带懒标记的并查集。
- 并查集核心作用:快速合并两个集合,快速找到元素所在的集合根节点,时间复杂度几乎是 O (1)。
- 懒标记的作用:给整个集合加值的时候,不用遍历所有元素,只需要给集合的根节点加一个标记 tag,代表整个集合都要加这个值,查询的时候再算进去,效率极高。
- 路径压缩:找根节点的时候,把元素的父节点直接指向根,同时更新元素到根的增量,下次查询更快,也保证了数值的正确性。
- 合并处理:合并两个集合的时候,调整被合并的根节点的 val 值,保证合并前后所有元素的活跃度不变,不会出现数值错误。
- 样例验证 :
- 合并 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。和样例输出完全一致。