第一次参加蓝桥杯软件赛 Python 大学 B 组,面对 8 道题从紧张到渐入佳境,每道题都像一场小型闯关。以下是从试题 A 到 H 的完整解析,包含题目解读、代码实现、答案和算法总结,希望能帮大家理清思路,掌握核心方法。
一、试题 A:攻击次数(结果填空)
题目
敌人初始血量 2025,三个英雄每回合攻击规则:
- 英雄 1:每回合固定 5 血
- 英雄 2:奇数回合 15 血,偶数回合 2 血
- 英雄 3:回合数 %3=1 时 2 血,%3=2 时 10 血,%3=0 时 7 血 求第几回合血量≤0。
解读
纯模拟题,按回合循环计算总伤害,累减血量直到结束。注意回合从 1 开始,条件判断别搞反(如奇数回合是turn%2==1
)。
代码
hp = 2025
turn = 0
while hp > 0:
turn += 1
dmg1 = 5
dmg2 = 15 if turn % 2 == 1 else 2
mod = turn % 3
dmg3 = 2 if mod == 1 else 10 if mod == 2 else 7
hp -= dmg1 + dmg2 + dmg3
print(turn) # 输出答案
答案
103
算法总结
- 模拟思想:按步骤循环,逐个回合计算状态变化。
- 条件分支:直接根据规则用
if-else
处理不同情况。
二、试题 B:最长字符串(结果填空)
题目
定义 “优美字符串”:长度 1 或前 n-1 个字符调整顺序后存在一个更短的优美字符串。从单词本中找最长的,字典序最小的。
解读
- 按长度排序单词,优先处理短字符串;
- 对每个单词,将前 n-1 个字符排序成集合,检查是否存在已记录的合法集合(动态规划)。
代码
with open('words.txt', 'r') as f:
words = [line.strip() for line in f]
# 按长度排序,同长度按字典序排序
words.sort(key=lambda x: (len(x), x))
max_len = 0
best_word = ''
# 记录每个长度的合法字符集合(排序后的字符串)
valid_sets = {1: set()} # 长度1的单词直接合法
for word in words:
n = len(word)
if n == 1:
valid_sets[1].add(''.join(sorted(word)))
if n > max_len:
max_len = n
best_word = word
else:
# 前n-1个字符排序后的字符串
prefix = ''.join(sorted(word[:-1]))
# 检查是否存在长度n-1的合法集合
if n-1 in valid_sets and prefix in valid_sets[n-1]:
if n not in valid_sets:
valid_sets[n] = set()
current_set = ''.join(sorted(word))
valid_sets[n].add(current_set)
if n > max_len or (n == max_len and word < best_word):
max_len = n
best_word = word
print(best_word) # 假设words.txt按题目示例输入,输出dbca
答案
假设输入示例中的单词,答案为 dbca(实际需根据题目给定的 words.txt 计算,此处以示例逻辑推导)。
算法总结
- 动态规划:用
valid_sets
记录各长度的合法字符集合,逐步推导更长字符串的合法性。 - 集合与排序:通过排序字符转为固定格式(如
aabc
→aabc
排序后aabc
),方便快速判断是否存在前驱。
三、试题 C:LQ 图形(程序设计)
题目
画 L 型图形:竖线高 h、宽 w,横线高 w、宽 v+w,每个笔划由 'Q' 组成。
解读
分两部分绘制:
- 竖线:h 行,每行 w 个 'Q';
- 横线:w 行,每行 v+w 个 'Q'(注意横线宽度是 v+w,不是 v)。
代码
w, h, v = map(int, input().split())
# 画竖线部分
for _ in range(h):
print('Q' * w)
# 画横线部分
for _ in range(w):
print('Q' * (v + w))
答案
按输入输出样例,正确代码即可生成对应图形(无具体数值答案,需通过测试用例验证)。
算法总结
- 模拟绘图:拆解图形为重复单元,用循环生成每一行,核心是找规律(行数和每行字符数)。
四、试题 D:最多次数(程序设计)
题目
统计字符串中能切割出最多的 “l、q、b” 各出现一次的子串(顺序不限),求最大次数。
解读
每个子串需包含 1 个 l、1 个 q、1 个 b,因此答案是三个字符出现次数的最小值。
代码
s = input().strip()
l = s.count('l')
q = s.count('q')
b = s.count('b')
print(min(l, q, b))
答案
如样例输入 “lqbblqblqlxqb”,输出3。
算法总结
- 统计贪心:直接统计字符次数,取最小值,无需复杂切割逻辑,抓住问题本质(每个子串必须包含三者各一次)。
五、试题 E:A・B Problem(程序设计)
题目
求满足内积\(X_A*X_B + Y_A*Y_B ≤ L\)的正整数向量对\((X_A,Y_A,X_B,Y_B)\)的数量。
解读
枚举\(X_A, Y_A\),计算内积上限后,求\(X_B, Y_B\)的可能取值。优化点:固定\(X_A, Y_A\),内积剩余值为\(L - X_A*X_B\),然后求\(Y_A*Y_B ≤ remaining\)的正整数解。
代码
L = int(input())
count = 0
for xa in range(1, L + 1):
for ya in range(1, L + 1):
inner = xa * xb + ya * yb # 这里写错了,正确应为枚举xb和yb时,计算xa*xb + ya*yb ≤ L
# 正确写法:枚举xa, ya,然后计算对于固定的xa, ya,xb和yb的可能取值
max_xb_ya = L // xa if xa != 0 else L # 其实应重新组织循环:
for xa in range(1, L + 1):
for ya in range(1, L + 1):
remaining = L # 内积上限是remaining
# 对于当前xa, ya,求所有xb, yb≥1,使得 xa*xb + ya*yb ≤ remaining
# 固定xb,求yb的最大值:yb ≤ (remaining - xa*xb)/ya,当xa*xb >= remaining时,yb无解
for xb in range(1, L + 1):
xb_part = xa * xb
if xb_part >= remaining:
break # yb至少1,xb_part + ya*1 > remaining时,后面的xb更大,无需继续
max_yb = (remaining - xb_part) // ya
if max_yb >= 1:
count += max_yb
else:
# yb至少1,所以当(remaining - xb_part) < ya时,无解
pass
# 还要考虑xb=1时,ya*yb ≤ remaining - xa*1,即yb ≤ (remaining - xa)/ya
# 上面的循环有问题,正确做法是:枚举xa, ya, xb,计算yb的可能数量
count = 0
for xa in range(1, L + 1):
for ya in range(1, L + 1):
for xb in range(1, L + 1):
x_part = xa * xb
if x_part >= L:
break # xb再大,x_part超过L,yb至少1,总和必超
max_yb = (L - x_part) // ya
if max_yb >= 1:
count += max_yb
print(count)
答案
样例输入 1
(L=2)输出 1
样例输入 2
(L=3)输出 5。
算法总结
- 三重枚举优化:通过提前 break 减少无效循环(如 xb 过大时提前终止),利用数学公式计算 yb 的可能数量,避免四重循环。
六、试题 F:园艺(程序设计)
题目
留下最多的树,满足等间隔(间隔 d≥1)且高度严格递增。求最大保留数量。
解读
- 枚举起点 i 和间隔 d,从 i 出发,每次跳 d 步,检查高度是否递增;
- 对每个 i,遍历所有可能的 d(d≤(n-1)/(k-1),k 为当前长度),记录最长递增序列。
代码
n = int(input())
h = list(map(int, input().split()))
max_len = 1 # 至少保留1棵树
for i in range(n):
for j in range(i+1, n):
d_pos = j - i # 间隔的位置差(步数)
current_len = 2
current_h = h[j]
pos = j + d_pos # 下一个位置
while pos < n:
if h[pos] > current_h:
current_len += 1
current_h = h[pos]
pos += d_pos
else:
break
max_len = max(max_len, current_len)
print(max_len)
答案
样例输入 6 3 5 4 7 6 7,输出 3(选第 1、3、5 棵树,间隔 2,高度 3→4→6)。
算法总结
- 双重枚举:枚举起点和初始间隔,然后按间隔递增检查后续节点,时间复杂度 O (n²),对 n=5000 可接受。
- 贪心思想:通过固定起点和间隔,贪心地寻找最长递增序列。
七、试题 G:书架还原(程序设计)
题目
通过交换任意两本书,求最少交换次数使排列有序。
解读
利用循环节理论:每个循环节(如 3→1→2→3)需要长度 - 1 次交换。总次数 = 元素总数 n - 循环节数量。
代码
n = int(input())
a = list(map(int, input().split()))
visited = [False] * (n + 1) # 元素编号1~n
cycle_count = 0
for i in range(n):
num = a[i]
if not visited[num]:
cycle_count += 1
# 遍历循环节
current = num
while not visited[current]:
visited[current] = True
current = a[current - 1] # a是0-based,元素是1-based,正确位置是current-1
print(n - cycle_count)
答案
样例输入 3 3 1 2,输出 2(循环节长度 3,3-1=2 次交换)。
算法总结
- 图论循环节:将排列视为有向图,每个节点指向其正确位置的元素,找出所有循环节,次数 = 节点数 - 循环节数。
八、试题 H:异或和(程序设计)
题目
计算所有数对\((i,j)\)的\((a_i ⊕ a_j) * (j-i)\)之和,i<j。
解读
直接双重循环 O (n²) 会超时(n=1e5 时 1e10 次运算),需按二进制位拆分,计算每一位对总和的贡献。
代码
n = int(input())
a = list(map(int, input().split()))
ans = 0
for bit in range(20): # 最多2^20,处理0~19位
mask = 1 << bit
cnt0 = cnt1 = 0
sum0 = sum1 = 0 # sum0记录0的位置和,cnt0记录0的个数,同理sum1
for idx in range(n):
if a[idx] & mask:
ans += cnt0 * mask * (idx - sum0) # 0的个数乘以当前位权,乘以位置差之和
sum1 += idx
cnt1 += 1
else:
ans += cnt1 * mask * (sum1 - idx) # 1的个数乘以当前位权,乘以位置差之和
sum0 += idx
cnt0 += 1
print(ans)
答案
样例输入 1
3
1 2 3
输出 8;
样例输入 2
4
9 8 7 6
输出 118。
算法总结
- 位运算优化:逐位处理,统计该位为 1 和 0 的位置,计算每对数的位置差对总和的贡献,将时间复杂度降至 O (n*20)。
总结:八道题的算法思想与核心考点
题号 | 题型 | 核心算法 / 思想 | 易错点 / 关键点 |
---|---|---|---|
A | 结果填空 | 模拟、条件分支 | 回合数从 1 开始,余数判断正确 |
B | 结果填空 | 动态规划、集合排序 | 字符排序后检查前驱集合,字典序处理 |
C | 程序设计 | 模拟绘图、规律拆解 | 横线宽度是 v+w,不是 v |
D | 程序设计 | 统计贪心 | 直接取三字符次数最小值,无需切割 |
E | 程序设计 | 枚举优化、数学推导 | 三重循环优化,避免无效计算 |
F | 程序设计 | 双重枚举、贪心 | 间隔是位置差,不是步数,递增条件严格 |
G | 程序设计 | 循环节理论、图论 | 正确转换 1-based 元素与 0-based 索引 |
H | 程序设计 | 位运算、逐位统计 | 按二进制位拆分,计算每一位的贡献 |
参赛建议
- 先易后难:优先解决填空和简单模拟题(如 A、C、D),确保基础分拿到手。
- 注意细节:输入输出格式、边界条件(如试题 E 中 xb=0 的情况不存在,因为是正整数)、索引转换(试题 G 的 1-based 元素)。
- 算法优化:遇到 O (n²) 可能超时的题(如 F、H),及时想到枚举优化(如试题 F 固定起点和间隔)或数学方法(如试题 H 的位运算)。
第一次参赛难免紧张,但通过逐题拆解会发现,蓝桥杯的核心还是考察基础算法的灵活运用。从模拟到贪心,从枚举到动态规划,每道题都是对编程思维的打磨。记住:代码不在复杂,正确和高效才是关键!