news 2026/10/24 6:10:31

蓝桥杯Python A组省赛复盘:动态规划、搜索与并查集模板

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
蓝桥杯Python A组省赛复盘:动态规划、搜索与并查集模板

点开这篇的朋友,估计是两类人:一类是刚考完第十三届蓝桥杯软件组省赛,正在对着Python大学A组的题目复盘;另一类是下一届要参赛,想找一套省赛高频算法模板当救命稻草。这篇不打算逐题复现标准答案,因为比赛结束后的真实题目版本并不统一,流传的回忆版也常有出入,更高效的方式是把我赛后复盘的几个高频题型、可运行的Python模板和考场踩坑点整理出来。本文适合第一次参赛的Python选手,也适合想找提分重点的算法初学者。

蓝桥杯的省赛和很多人印象里的“算法竞赛”不太一样,它更看重“在规定时间内稳定拿到尽可能多的分”,而不是非要写出满分正解。所以这篇不完全题解的核心思路是:把稳拿分的基础模板全部吃透,再把几个高频考点的易错点讲清楚。你不需要背几百道题,掌握下文这些内容,省赛拿省奖就已经比较稳了。

1. 这是一场怎样的比赛:先弄清Python A组在考什么

1.1 题型分布与得分策略

Python大学A组的省赛题目数量一般在10题左右,题型大致可以分成三类:纯模拟/暴力题、经典算法题、需要一点数学观察的题。纯模拟题通常占2到3题,这类题只要你读懂题、细心实现就能拿满分,是整张试卷的“保底分”。经典算法题一般占4到5题,动态规划、搜索、并查集、图论、字符串处理都是常客。剩下两三题会比较灵活,可能考察数论、组合数学,或者一道需要自己设计状态的DP。

这里必须说一个很多新手理解错的地方:蓝桥杯不是ACM,10题不需要全部做出来。每题的分值不一样,而且部分正确也能拿到部分分。比如一道需要用动态规划解决的题目,你直接写一个DFS暴力搜索,虽然跑不过大数据,但小规模用例能对,系统就会给一部分分数。这很关键,意味着考场上的最优策略是“先把所有题都看一遍,能拿的暴力分全部拿掉,再回头优化”。

我第一次参赛的时候就犯过错:前面有一道模拟题卡了四十分钟,结果后面几道简单题没时间做,分数非常难看。后来我学乖了,拿到试卷先花五到八分钟通读所有题目,按“拿分性价比”排个序。一道十五分钟能写完的暴力题和一道需要一小时才能写出的正解,前者的优先级一定更高。这不是投机,这是竞赛里的项目管理。

1.2 考场环境和程序运行速度带来的隐性坑

Python组的考场环境其实很朴素,通常只有标准的Python解释器和一些常用库(比如math、itertools、collections、datetime),没有网络,也没有你平时依赖的第三方扩展包。这意味着你在本地用的某些科学计算库在考场上根本跑不了,平时练习必须刻意远离它们。同时,评测机的性能和本地电脑不一定一样,Python本身跑得慢,如果不注意算法复杂度和输入输出方式,很容易出现“本机秒出结果,提交后全部超时”的情况。

Python在竞赛里有一个巨大优势,就是整数长度没有任何限制,这一点和C++完全不同。遇到高精度计算题,直接用int做加法、乘法就行,不需要手写大数模板。但Python的劣势也同样明显:普通纯Python循环跑一百万次已经很吃力,三层嵌套循环基本就是灾难。所以能用内置函数解决的绝不用手写循环,比如列表去重用set,统计次数用Counter,组合数直接用math.comb。这些内置实现通常比你自己写的循环快数倍甚至几十倍。

运行速度问题还会影响算法选型。同样是一道搜索题,能用BFS就不用DFS,能迭代就不用递归,能剪枝就不要裸搜。后面我会专门讲代码模板,先把这些原则记在心里。

2. 省赛最常撞上的两类算法:动态规划与搜索

2.1 动态规划:状态定义、转移方程和那两个经典错误

动态规划是蓝桥杯省赛的绝对核心,几乎每年都考,而且往往不止一道。背包问题、最长上升子序列、路径方案数、区间DP都出现过。动态规划的难点其实不在转移方程,而在两件事:第一,你能不能看出这是一道DP题;第二,你能不能把状态定义得足够简洁。

先举一个最典型的01背包例子:有一组物品,每个物品有重量w和价值v,背包容量是C,问能装下的最大价值。状态定义用dp[j]表示容量为j时能获得的最大价值,转移的时候从大到小更新容量。为什么从大到小?因为这样每个物品在当前轮里只会被使用一次;如果从小到大更新,后面的状态可能又用到这个物品,等价于无限拿取,那就变成完全背包了。这个倒序遍历是背包题最经典的坑,我见过很多人在这个地方栽跟头。

def zero_one_knapsack(weights, values, capacity): dp = [0] * (capacity + 1) for w, v in zip(weights, values): for c in range(capacity, w - 1, -1): dp[c] = max(dp[c], dp[c - w] + v) return dp[capacity]

另一个常见DP错误是漏掉边界状态。比如“从左上角走到右下角,只能向下或向右,问路径方案数”,状态很简单:dp[i][j] = dp[i-1][j] + dp[i][j-1]。但如果你不把第一行和第一列初始化为1,结果全是0。这种错误特别隐蔽,因为小规模样例可能碰巧能过,直到大数据才暴露。我的习惯是写DP前先在草稿纸上画出小表,手动算出前几个数,再跑代码对比,能排查掉大部分边界问题。

还有一点很多人忽略:DP题目经常要求取模(一般对10的9次方加7取模)。如果你忘了取模,结果会非常大,Python虽然不会溢出,但输出超过预期后直接判错,而且你很难想到是这个原因。判断方法是看题目的“输出格式”里是否写着“对结果取模”。只要写了,每一步加法、乘法都必须取模,不能只在最后取一次,因为Python里的数字再大也不会报错,但中间过程可能已经多到让后面的计算变得极其缓慢。

2.2 搜索/回溯:方向数组、剪枝与递归深度限制

搜索题也是省赛常客,尤其是地图类题目:迷宫最短路径、连通块数量、岛屿面积、矩阵中的路径等等。地图题核心就两个:方向数组和访问标记。方向数组就是上下左右四个方向的偏移量:

DIRECTIONS = [(1, 0), (-1, 0), (0, 1), (0, -1)]

用循环遍历方向数组,判断新位置是否越界、是否已访问、是否为可走区域。越界判断很多人习惯单独写四个if,代码又臭又长还容易漏。统一写成if 0 <= nx < n and 0 <= ny < m才是正解,简洁且不容易错。

连通块计数类题目,我强烈建议用栈式DFS或者BFS,不要用递归。原因很现实:Python默认递归深度上限只有1000,地图大一点、连通区域长一点,递归直接抛“RecursionError”,当场送走一整题。如果你确实想用递归,开头记得加上import sys; sys.setrecursionlimit(1 << 25),但这只能缓解,不能根治。用显式栈模拟递归,深度多大都没问题:

def count_islands(grid, n, m): visited = [[False] * m for _ in range(n)] ans = 0 for i in range(n): for j in range(m): if grid[i][j] == 1 and not visited[i][j]: ans += 1 stack = [(i, j)] visited[i][j] = True while stack: x, y = stack.pop() for dx, dy in DIRECTIONS: nx, ny = x + dx, y + dy if 0 <= nx < n and 0 <= ny < m and not visited[nx][ny] and grid[nx][ny] == 1: visited[nx][ny] = True stack.append((nx, ny)) return ans

如果是迷宫最短路,推荐BFS加记录距离的二维数组,第一次到达终点时的距离就是最短路径。BFS天然自带“逐层扩展”的特性,不需要像DFS那样反复回溯比较,代码也更简单。

搜索题还有一个核心技巧:剪枝。最常用的是最优性剪枝:如果当前已经找到了一条长度为K的路径,而当前在搜索过程中累计步数已经大于等于K,就直接return,不再往下走。这个技巧在走迷宫、拼接木棍这类题里能把指数级搜索砍掉一大半执行时间。另一个是可行性剪枝:预估一下剩余路程最少还要多少步,如果当前步数加上预估步数已经超过已知最优解,同样提前退出。

3. 可以直接拿去用的高频代码模板

3.1 并查集模板:连通性问题的拿分利器

并查集在省赛里的出现频率非常高,通常以“判断两点是否连通”“合并集合”“计算连通分量数量”等形式出现。它的代码量不大,但路径压缩和按秩合并两个优化最好写全。路径压缩让find操作几乎达到常数时间,按秩合并防止树退化成链表。模板如下:

class DSU: def __init__(self, n): self.parent = list(range(n)) self.size = [1] * n def find(self, x): while self.parent[x] != x: self.parent[x] = self.parent[self.parent[x]] x = self.parent[x] return x def union(self, a, b): ra, rb = self.find(a), self.find(b) if ra == rb: return False if self.size[ra] < self.size[rb]: ra, rb = rb, ra self.parent[rb] = ra self.size[ra] += self.size[rb] return True

使用并查集最常见的错误是union前忘记find,直接把两个原始节点接起来。比如parent[a] = b,这样会导致下一次find(a)时跳到一个错误的位置。正确写法永远是先得到两个根节点,再判断是否需要合并。另外,如果你需要统计连通分量数量,不需要额外维护变量,直接遍历所有节点统计find(i) == i的个数即可。

很多图论题用并查集能写出非常简洁的解法,比如“给出一组边,问哪些边会形成环”,按顺序遍历每条边,union之前先判断两个端点是否已经在同一集合里,如果是,说明这条边会成环。这个思路比跑一遍图遍历快得多,而且不容易写错。

3.2 数学与输入输出模板:用Python特性降低编码量

省赛里数学类题目主要涉及素数、最大公约数、进制转换、组合数。Python的math库已经集成了大量高频函数,比如math.gcd、math.lcm、math.comb、math.perm。组合数在Python 3.8以后可以直接用,题目如果要求计算排列组合数,不要自己写递推循环,直接调用就行。不过要注意math.comb(1000, 500)这种极端计算虽然能出结果,但速度偏慢,正式比赛如果对性能敏感,还是需要预处理阶乘或杨辉三角。

素数筛模板也非常常用,埃氏筛写法简单,数据范围在10的6次方以内完全够用:

def sieve(n): is_prime = [True] * (n + 1) is_prime[0] = is_prime[1] = False for i in range(2, int(n ** 0.5) + 1): if is_prime[i]: for j in range(i * i, n + 1, i): is_prime[j] = False return [i for i in range(n + 1) if is_prime[i]]

输入输出优化是很多人忽视的提分点。用input()逐行读数据,在几百行数据时没问题,但上万行时速度会明显拖慢整个程序。竞赛中标准的输入输出模板是:

import sys def main(): data = list(map(int, sys.stdin.buffer.read().split())) idx = 0 n = data[idx]; idx += 1 # 后续按需从data中取值 ans = [] ans.append(str(result)) sys.stdout.write("\n".join(ans)) if __name__ == "__main__": main()

sys.stdin.buffer.read().split()会一次性读入所有内容并按空白字符切分,再转成整数列表,速度比逐行读快很多。输出的sys.stdout.write配合列表收集也是一样,避免多次调用print。这个模板在省赛的“大数据量输入输出”题里能救回好几分,一定要背下来。

4. 复盘时最容易发现的失误与排查方法

4.1 失分高发区自查清单

赛后复盘的时候,我发现失分点其实很少出现在“不会做”的题目上,更多是栽在“明明会做但写错了”的地方。这里整理一份自查清单,每次提交前对照检查,能省下大量罚分。

第一是读题漏条件。题目描述里往往藏着关键限制,比如“输出结果需要保留两位小数”“答案对1000000007取模”“所有坐标从1开始”等。这些条件如果在实现时没处理,样例可能看不出来,但正式评测会直接判错。

第二是数组越界。Python的优势是列表越界会报错而不是像C++那样静默出错,但有些时候越界不会抛异常,而是取的负索引值。比如一个长度为5的数组,访问arr[-1]不会报错,返回的是最后一个元素,这会在边界数据上产生完全错误的结果。排查办法是统一在访问前做索引范围判断。

第三是变量名覆盖。Python是动态语言,很容易在循环里不小心覆盖掉函数名或者全局变量。比如某次循环变量叫s,而代码后面还需要用字符串s,结果就把原来的值覆盖掉了。我的习惯是循环变量尽量用i、j、k这种单字母,模块级别的变量才用有意义的名字。

第四是递归深度。前面提过,Python默认递归上限很小,如果你用递归写DFS,大数据直接崩。这个错误往往不是逻辑问题,而是环境限制。开头的sys.setrecursionlimit必须写成全局导入,函数内部的临时恢复没有用。

第五是浮点数精度。有些题目涉及浮点比较,不要用==直接判断是否相等,正确的做法是设定一个极小误差,比如abs(a - b) < 1e-9。更稳妥的办法是能避开浮点就避开,比如把加法比较变成交叉相乘比较整数,精度问题直接消失。

4.2 现场调试纪律和边界测试技巧

比赛现场没有调试器,也不方便断点跟踪,我用的是最原始的print大法,但有纪律。所有调试print统一加前缀,比如在行首输出DEBUG:,最后提交前全局搜索替换成空字符串。千万不要print到一半就交,结果评测输出里混入调试信息,那一定会被判错。有时候本地忘删调试输出,提交后才发现,白白丢分。

边界测试是另一个容易偷懒但极其重要的环节。除了题目给的样例,自己至少再补三组:最小数据、最大数据、特殊形状的数据。最小数据比如n=1、m=1、数组只有一个元素,很多初始化问题只有在这种极简单的情况下才会暴露。最大数据用来测复杂度和性能,如果连最大数据都能秒出结果,基本不用担心超时。特殊形状数据,比如全0、全1、乱序、升序、降序,这些能帮你发现逻辑里的隐含假设。比如判断严格递增时,是不是漏掉了相等的情况。

还有一个我踩过很多次的坑:多组测试数据。有些题目会连续输入多组数据,以某个特殊值作为结束标志。刚接触竞赛的人很容易只处理一组就退出,或者读数据时格式错位。遇到这种题,建议用while True配合try / except EOFError,或者直接用while判断结束标志,确保每组数据都完整处理后再进入下一组。

5. 赛后我的一些实话

5.1 省赛拿奖的难度比想象中低,但前提是肯复盘

每次比完赛,网上都有人晒满分代码、暴力全解,给你一种“人人都是大佬”的错觉。实际上省赛的获奖率并不低,尤其是省一和国赛门票的竞争,真正比的是谁能少犯错,而不是谁能写出最惊艳的解法。我和身边的选手复盘过几次,发现进国赛的朋友并不是每道题都会,而是他们“会做的题几乎全部拿满,不会的题也能拿部分分”。

所以赛前刷题不要只追求数量,反而要重视“纠错质量”。每道错题都值得花时间弄清楚:是算法不会,还是边界没考虑,还是实现细节出错。把错误归类,你很快就能画出自己的薄弱点雷达图,针对性补漏比盲目刷两百道题有用得多。

5.2 给下一届选手的三条实用建议

第一条,把卷子里的“模拟题”当成送分题练。模拟题不考算法深度,考细心程度。平时练习时给自己规定:模拟题一次写对,不回头改。逼自己养成读全题、划重点条件的习惯。

第二条,背模板但不能只背模板。模板的作用是减少考场上的低级错误,但如果你不理解模板为什么这样写,一旦题目变形就不会套用。比如并查集的路径压缩,你要明白它压缩的是find过程中沿途节点,才能在遇到“带权并查集”这种变体时不慌。

第三条,重视Python的工程化写法。列表推导、字典的get方法、collections模块的defaultdict和deque,这些在竞赛里能极大缩短编码时间。我没有夸张,熟练使用这些内置功能之后,同样一道题别人写50行,你用15行就能实现,出错概率也低很多。省赛的时间就那么几个小时,编码速度本身就是竞争力。

第四次参赛的时候,我最深的体会是:蓝桥杯与其说是一场智力的较量,不如说是一场“准备度”的较量。题目本身没有刁钻到让人完全无从下手,但考场上环境陌生、时间紧张、心态波动,任何一个环节都可能让你丢掉本该拿到手的分数。把基础模板练到肌肉记忆,把常见错误变成条件反射式的检查项,分数自然就上去了。希望这份不完全题解,能让你在下一场比赛里少踩几个我踩过的坑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 6:08:30

Protobuf序列化实战:从.proto语法到高性能二进制协议设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 6:07:36

Java自动抢票工具拆解:验证码识别与无人值守下单链路解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 6:07:25

Filebeat处理JSON日志:字段裁剪、扁平化与拆分实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 6:06:49

Java面试必问:BIO/NIO/AIO与Netty核心原理35题详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 6:02:29

Qt网络调试助手:集成TCP/UDP客户端与服务端的嵌入式联调工具

简介&#xff1a;这是一款基于Qt开发的跨平台网络调试助手工具&#xff0c;面向嵌入式开发、物联网设备联调及Qt初学者&#xff0c;解决TCP/UDP通信场景下的协议收发、设备模拟与多连接管理等实际调试难题。资源包共35个文件&#xff0c;含8个核心cpp/h源码文件&#xff08;如t…

作者头像 李华
网站建设 2026/10/24 6:01:26

基于动态窗口算法的AGV仿真避障:路径规划与实时避障实现

最近在折腾AGV仿真避障&#xff0c;项目名就叫“基于动态窗口算法的AGV仿真避障&#xff1a;路径规划与实时避障”。说实话&#xff0c;做移动机器人导航的人对这个算法应该都不陌生&#xff0c;但真正从零搭一版能跑、能调、能复现的仿真&#xff0c;还是踩了不少坑。这篇博文…

作者头像 李华