☰
蓝桥杯Python组模板:从IO到DP的备赛速查与实战策略
2026/10/7 1:30:37 网站建设 项目流程

1. 为什么Python组更该有一套自己的模板

蓝桥杯的Python组和C++组,最大的差别不是语言语法,而是时间预算的分配方式。同样一道题,C++选手可能花十分钟敲完然后纠结边界,Python选手往往要在敲之前就想清楚用什么容器、怎么读输入、会不会超时。这个差异决定了Python选手的模板不是“背代码”,而是把那些确定不变的部分提前固化,让赛场上只剩思考。

我前后打过几场,也带过学弟学妹备赛,最大的感受是:真正拉开差距的不是谁多会一个算法,而是谁在开考后的前二十分钟没有被输入格式、递归深度、输出拼接这些杂事拖住。一套趁手的python蓝桥杯模板,本质是把自己从重复劳动里赎出来,把脑力留给建模和推导。

这套东西适合谁?打过一点算法题、知道01背包动态规划python怎么写、但一到比赛就手忙脚乱的选手;也适合刚学完python入门内容、想试试水的新手。新手照着抄能拿住基础分,老手把它当速查手册,赛前扫一遍查漏补缺。下面我就按“地基—基础—进阶—策略—排查”的顺序,把自己这些年攒下来的东西完整摊开讲一遍。

1.1 Python组和C++组的差异到底在哪

先说清楚差距有多大,才好判断模板该往哪个方向堆。同样的算法复杂度,Python的执行速度大概只有C++的十分之一到三十分之一,具体倍数取决于循环里干了多少事。这意味着C++选手敢写的 O(n²) 暴力,n 到五千还行,n 到两万就悬;而Python里 n 到三千的 O(n²) 就已经接近一秒的边界了。

但Python有两个C++比不了的优势。第一是大整数原生支持,题目里出现阶乘、2的幂次、超长整数运算时,Python写起来几乎零成本,C++得手写高精度。第二是标准库极其丰富,itertools、collections、heapq、bisect、datetime这些模块直接调用,省下大量造轮子的时间。

所以Python组的策略很明确:能靠库解决的绝不手写,能靠数学推导降复杂度的绝不上数据结构,实在要写暴力就用库函数把常数压到最低。模板要围绕这三条来组织,而不是把C++的板子翻译一遍。

1.2 模板不该是背诵材料,而是“电路板”

很多人对模板有误解,觉得是把一堆代码存成文本,比赛时复制粘贴。真到了赛场上你会发现,题目条件千奇百怪,直接粘贴的代码十有八九要改三四行,改的过程反而更容易出错。

我更愿意把模板理解成电路板:每个模块只负责一件事,接口清晰,插上去就能用,剩下的逻辑你自己接线。比如二分查找模板,它不负责帮你判断该用左边界还是右边界,那是你的活;它只保证在数组有序的前提下,返回第一个不小于 x 的位置,且不会死循环。这个边界划清楚了,模板才有复用的价值。

按照这个思路,我把模板分成三层:第一层是IO与运行环境,属于每道题都要用的地基;第二层是数学和序列处理的基础件,出现频率极高;第三层是图论、动态规划这类成套算法,属于需要整块调用的模块。三层分开管理,赛前按层检查,比重头背一遍效率高得多。

1.3 备赛节奏与模板的分层策略

备赛时间怎么切,直接决定模板最终长什么样。我的建议是倒着来:先明确比赛时长和题型分布,再倒推需要哪些能力。

通常填空题占一部分,这部分对速度要求不高,可以用暴力枚举甚至手算辅助,模板需求集中在日期计算、进制转换、排列枚举上。编程题占大头,难度分层明显,前面的签到题基本是模拟加简单数学,后面的压轴题才涉及图论和DP。所以模板的优先级排序应该是:输入输出 > 数学工具 > 序列处理 > 搜索 > 图论 > DP。

按这个顺序投入时间,收益是最高的。我见过不少人一上来就啃线段树和网络流,结果比赛时连多组输入的结束条件都处理错,白白丢分,这个账算不过来。

2. 环境与IO:模板的地基

这部分最不起眼,却最容易翻车。很多人本地跑得好好的,交上去全是运行错误,八成问题出在这里。

2.1 Python环境与IDE的配置要点

先说python安装。蓝桥杯的评测机用的是官方指定的Python版本,这几年基本是Python 3.8或更新的3.x版本,具体以当年通知为准。你在本地准备环境时,尽量对齐这个版本号,别用太新的特性,比如海象运算符在旧版本上会直接语法错误。

安装的时候有一个坑必须提:Windows下安装包默认不勾选“Add Python to PATH”,不勾的话命令行里敲python会提示找不到命令。这个不是致命问题,但对新手来说很折腾。安装完成后在命令行验证python --version和pip --version都能正常输出,才算装好。

编辑器方面,pycharm配置python环境对新手最友好,新建项目时选好解释器就完事,代码补全和调试功能都很强。如果你习惯轻量一点的,vscode python环境配置也不难:装Python扩展,按 Ctrl+Shift+P 选解释器,再配一个launch.json就能断点调试。我自己的习惯是赛前用PyCharm对着题目练,因为它的调试器能直接看变量,排查逻辑错误比print快得多。

注意:赛前一定要在本地完整模拟一次“新建文件—写代码—读输入文件—输出到控制台”的流程。有些评测机要求从标准输入读、标准输出写,本地如果用文件读写测试,容易忘记改回来。

2.2 输入输出模板:快读快写与结束符处理

蓝桥杯如何读取输入python这个问题,在搜索里出现频率极高,说明踩坑的人多。核心结论只有一句:优先用sys.stdin而不是input()。

input()每次调用都要做一次字符串剥离和类型转换准备,数据量大时开销很明显。十万行输入用input()可能要一秒多,换成一次性读取再切分,往往降到零点几秒。下面是我常用的两个版本。

第一个版本适合单组数据、格式规范的题目:

import sys def main(): data = sys.stdin.buffer.read().split() n = int(data[0]) a = list(map(int, data[1:1 + n])) # 后续逻辑 print(sum(a)) if __name__ == "__main__": main()

sys.stdin.buffer.read()返回字节串,.split()按空白切分,这样换行、空格、制表符、多余空行全部自动处理掉。这个写法几乎能应付九成的蓝桥杯题型。

第二个版本适合多组数据、遇到终止条件才停的题目:

import sys def main(): for line in sys.stdin: line = line.strip() if not line: continue n, m = map(int, line.split()) if n == 0 and m == 0: break # 处理这一组 print(n + m) if __name__ == "__main__": main()

这里有两个细节值得说。一是strip()之后要判断空行,因为有些题目最后会多一个空行,不处理会报错。二是终止条件的判断要写在处理逻辑之前,否则最后一组会多余输出一次。

输出端也有讲究。多次print()会频繁触发写操作,数据量大时改用拼接再一次性写出:

sys.stdout.write("\n".join(map(str, ans)) + "\n")

注意这里手动补了个换行符,因为很多评测机不介意行尾有没有换行,但有些题目对输出格式敏感,补上更保险。另外,整数转字符串这一步用map(str, ...)交给内置函数,比列表推导式快一些。

2.3 递归深度、数据规模与耗时预估

Python默认递归深度是一千层左右,深搜稍微深一点就RecursionError。解决办法是在程序开头加一行:

import sys sys.setrecursionlimit(1 << 20)

1 << 20是一百多万,足够应付绝大多数搜索题。但要注意,递归深度调大之后如果栈真的爆了,程序可能直接崩溃而不是抛异常,排查起来更难。我个人的经验是,深度可能超过五万时就不要硬用递归了,改成显式栈的迭代写法更稳。

再说耗时预估,这是决定要不要换算法的关键。下面这张表是我本地实测加比赛体感总结的,供参考:

数据规模可接受的复杂度典型做法
n ≤ 20O(2ⁿ)、O(n!)状压DP、全排列枚举
n ≤ 100O(n³)Floyd、三重循环DP
n ≤ 2000O(n²)二维DP、朴素匹配
n ≤ 10⁵O(n log n)排序、二分、堆、树状数组
n ≤ 10⁶O(n)前缀和、双指针、线性筛
n ≥ 10⁷通常无解需要数学推导降维

这张表的用法是:读完题先估上界,再对照表格选算法。如果发现上界落在红色区域,说明暴力必挂,得回去重新推公式或者找性质。我遇到过的典型场景是一道求区间内满足某种性质的数的个数,直接枚举是 O(n),但 n 到了十的九次方,最后是靠数位统计加前缀和降到了 O(log n)。

提示:本地测耗时别用time.time()粗略算,用time.perf_counter()精度更高。测量时把输入读取也算进去,因为那部分是真实开销。

3. 高频基础模板:把常用操作变成肌肉记忆

基础模板的价值在于“不用想”。写的时候手指自动敲出来,脑子全部留给题目逻辑。这一层我按数学、序列、字符串三块来整理。

3.1 数学类模板:质数、幂次与组合数

先说最大公约数和最小公倍数。Python 3.9之后math.gcd和math.lcm都支持多参数,直接调用即可。旧版本只有math.gcd,最小公倍数要自己写:

from math import gcd def lcm(a, b): return a // gcd(a, b) * b

注意这里先除后乘,避免a*b溢出。虽然Python大整数不会溢出,但早除可以减小中间结果的位数,对速度有轻微好处。

快速幂是另一个高频件。求 a 的 b 次方对 mod 取模,标准写法是用二进制拆分:

def qpow(a, b, mod): r = 1 a %= mod while b: if b & 1: r = r * a % mod a = a * a % mod b >>= 1 return r

循环次数是 b 的二进制位数,b 到十的十八次方也只需要六十次循环。这个模板还经常被改造用来求逆元,条件是 mod 为质数且 a 不是 mod 的倍数,此时qpow(a, mod - 2, mod)就是 a 的乘法逆元。

质数筛我更推荐线性筛,虽然代码比埃氏筛长一点,但复杂度是严格的 O(n):

def linear_sieve(n): is_comp = bytearray(n + 1) primes = [] for i in range(2, n + 1): if not is_comp[i]: primes.append(i) for p in primes: if i * p > n: break is_comp[i * p] = 1 if i % p == 0: break return primes

bytearray用来做标记数组,内存占用只有列表的八分之一左右,n 到一千万也能扛住。这个筛法顺便还能求每个数的最小质因子,改写一下就能做质因数分解模板。

组合数方面,小范围直接用递推,n 到几千都够用:

MAXN = 2005 C = [[0] * MAXN for _ in range(MAXN)] for i in range(MAXN): C[i][0] = 1 for j in range(1, i + 1): C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD

如果 n 很大但查询次数少,用阶乘加逆元更快;如果查询次数极多且模数固定,可以预处理阶乘和逆元数组。这三种写法各有适用场景,别只会一种。

3.2 序列处理模板:前缀和、差分与二分

前缀和解决的是“区间求和”问题,一维版本一眼就会,真正容易写错的是二维版本:

n, m = 5, 5 a = [[0] * (m + 1) for _ in range(n + 1)] pre = [[0] * (m + 1) for _ in range(n + 1)] for i in range(1, n + 1): row = pre[i] prev = pre[i - 1] cur = a[i] for j in range(1, m + 1): row[j] = prev[j] + row[j - 1] - prev[j - 1] + cur[j]

查询子矩形 (x1,y1) 到 (x2,y2) 的和就是pre[x2][y2] - pre[x1-1][y2] - pre[x2][y1-1] + pre[x1-1][y1-1]。这个公式容易记反,我的土办法是画个田字格,把多减的那块补回来。

差分是前缀和的逆运算,用来处理“区间加、最后统一查询”的题目。一维差分在左端点加、右端点后一位减,最后做一次前缀和还原。二维差分稍微绕一点,四个角分别加加减减,写的时候对着纸推一遍比硬记靠谱。

二分我准备了三个版本,按需取用:

from bisect import bisect_left, bisect_right # 找第一个 >= x 的位置 i1 = bisect_left(a, x) # 找第一个 > x 的位置 i2 = bisect_right(a, x) # 手写版本,方便改成自定义条件 def lower_bound(a, x): lo, hi = 0, len(a) while lo < hi: mid = (lo + hi) // 2 if a[mid] < x: lo = mid + 1 else: hi = mid return lo

库函数的版本更快,但只能用于有序数组上的值比较。有些题目要求“第一个满足某条件的位置”,条件不是简单的比大小,那就得手写。手写的时候关键是循环不变量要想清楚:lo左边永远是false,hi右边永远是true,这样退出循环时lo == hi就是答案。

二分答案是我用得最多的技巧。题目问“最小的最大值”或“最大的最小值”,十有八九是二分答案加判定函数。判定函数通常贪心就能写,整体复杂度 O(n log V),V 是答案范围。这类题目的难点不在二分本身,而在判定函数怎么写才不漏解,建议写完先跑几组小数据对着暴力验证。

3.3 字符串与日期处理模板

字符串题在蓝桥杯里出现频率不低,尤其是填空题里的回文、子串计数。Python的字符串切片极其方便,s[::-1]就是反转,判断回文直接s == s[::-1]。但要小心切片产生新对象,循环里大量切片会拖慢速度,这时改成双指针判断更合适。

KMP的前缀函数模板我建议备一份,虽然用s in t能解决大部分查找问题,但涉及“最短循环节”“前后缀匹配长度”时就得靠它:

def prefix_function(s): n = len(s) pi = [0] * n for i in range(1, n): j = pi[i - 1] while j > 0 and s[i] != s[j]: j = pi[j - 1] if s[i] == s[j]: j += 1 pi[i] = j return pi

算完之后,最小循环节长度是n - pi[n-1],前提是 n 能被这个长度整除,否则说明不循环。这个结论在填空题里直接省掉一大段推导。

日期类题目几乎每年都有,手算容易错,用datetime最稳:

from datetime import date, timedelta d = date(2000, 1, 1) end = date(2024, 12, 31) cnt = 0 while d <= end: if d.weekday() == 0: # 0是周一 cnt += 1 d += timedelta(days=1) print(cnt)

weekday()返回 0 到 6,分别对应周一到周日,isoweekday()返回 1 到 7。这两个容易混,我习惯统一用weekday(),在纸上标好对应关系。闰年判断直接用calendar.isleap(y),别自己写y % 4 == 0 and y % 100 != 0这种,容易漏条件。

注意:datetime支持的范围是公元1年到9999年,题目如果涉及超大年份或者需要按自定义规则算日期,还是得手写。手写时把月份天数存成列表,闰年时改二月,这个套路比任何公式都好用。

4. 进阶算法模板:搜索、图论与动态规划

这一层是拉开分数的关键。我的建议是每个大类都练到能默写,但不要贪多,先把最高频的那几个吃透。

4.1 搜索模板:DFS、BFS与剪枝

DFS的骨架非常固定,本质是递归加状态标记:

def dfs(u, depth): if depth == target: # 处理答案 return for v in graph[u]: if not visited[v]: visited[v] = True dfs(v, depth + 1) visited[v] = False

这里最容易错的地方是回溯时机。标记要在进入递归前设置,取消要在递归返回后立刻执行。如果中途有return,记得在返回前取消标记,否则状态会污染后续搜索。我踩过这个坑,一道排列题死活少几种情况,查了半小时才发现是提前返回时漏了回溯。

BFS用于求最短步数,队列用collections.deque而不是列表,因为列表的pop(0)是 O(n) 的,数据量一大就成瓶颈:

from collections import deque def bfs(start, n, m, grid): dist = [[-1] * m for _ in range(n)] q = deque([start]) dist[start[0]][start[1]] = 0 dirs = ((1, 0), (-1, 0), (0, 1), (0, -1)) while q: x, y = q.popleft() for dx, dy in dirs: nx, ny = x + dx, y + dy if 0 <= nx < n and 0 <= ny < m and dist[nx][ny] == -1: dist[nx][ny] = dist[x][y] + 1 q.append((nx, ny)) return dist

方向数组写成元组的元组,比列表稍微省一点内存,遍历也略快。边界检查写成0 <= nx < n这种链式比较,Python里比nx >= 0 and nx < n快一点,也别小看这一点,BFS里这个判断会执行几百万次。

剪枝是搜索题拿分的关键。常见的剪枝手段有三种:可行性剪枝、最优性剪枝、搜索顺序优化。可行性剪枝是判断当前状态是否已经不可能满足条件,直接返回;最优性剪枝是当前代价已经超过已知最优解就停;搜索顺序优化是把选择少的节点先搜索,减少分支。三种结合用,往往能把指数级搜索压到可接受范围。

4.2 图论模板:并查集、最短路与拓扑排序

并查集用得极广,从连通块计数到最小生成树都靠它。迭代版避免递归深度问题:

parent = list(range(n + 1)) def find(x): while parent[x] != x: parent[x] = parent[parent[x]] x = parent[x] return x def union(a, b): ra, rb = find(a), find(b) if ra != rb: parent[ra] = rb

parent[x] = parent[parent[x]]这行是路径压缩,把当前节点直接挂到祖父节点上,下次查找就快一层。如果要按秩合并,再维护一个rank数组,把矮树挂到高树上。绝大多数题目只做路径压缩就够了。

单源最短路首选Dijkstra加堆:

import heapq def dijkstra(s, graph, n): INF = float('inf') dist = [INF] * (n + 1) dist[s] = 0 pq = [(0, s)] while pq: d, u = heapq.heappop(pq) if d > dist[u]: continue for v, w in graph[u]: nd = d + w if nd < dist[v]: dist[v] = nd heapq.heappush(pq, (nd, v)) return dist

if d > dist[u]: continue这行是“懒惰删除”,用来跳过堆里的过期条目。没有这行程序结果也对,但会慢不少,因为同一个节点可能被反复弹出。

多源最短路用Floyd,三重循环注意把k放最外层,写错了结果会很诡异:

for k in range(n): for i in range(n): for j in range(n): if d[i][k] + d[k][j] < d[i][j]: d[i][j] = d[i][k] + d[k][j]

内层加个if d[i][k] == INF: continue能省不少时间。另外Python的三重循环在 n 到五百时就有点吃力了,n 到一千基本必超时,这时候要考虑换算法或者用其他技巧。

拓扑排序用入度队列:

from collections import deque def topo_sort(n, adj, indeg): q = deque(i for i in range(1, n + 1) if indeg[i] == 0) order = [] while q: u = q.popleft() order.append(u) for v in adj[u]: indeg[v] -= 1 if indeg[v] == 0: q.append(v) return order if len(order) == n else []

返回空列表表示存在环,这个判断在题目里经常用到,比如检测依赖关系是否矛盾。

4.3 动态规划模板:背包、线性与区间

背包是DP里出现频率最高的题型。01背包一维滚动的写法必须背下来:

dp = [0] * (V + 1) for w, val in items: for j in range(V, w - 1, -1): if dp[j - w] + val > dp[j]: dp[j] = dp[j - w] + val

内层逆序是关键,保证每件物品只用一次。如果写成正序就变成了完全背包,也就是每件物品可以用无限次。这个区别我在初期老是记混,后来用一句口诀记:逆序是01,正序是完全。

多重背包如果每件物品数量少,直接拆成多个01背包即可。数量大时用二进制拆分,把 k 件物品拆成 1、2、4、8…… 这样的组合,能把复杂度从 O(V·Σk) 降到 O(V·Σlog k):

new_items = [] for w, val, cnt in items: k = 1 while cnt > 0: take = min(k, cnt) new_items.append((w * take, val * take)) cnt -= take k <<= 1

这段代码很短,但理解起来需要想一下:任何数量都能表示成若干个2的幂次之和,所以拆完之后每种组合都能凑出来。

线性DP里我常备的是最长递增子序列,贪心加二分的写法是 O(n log n):

from bisect import bisect_left def lis(a): tails = [] for x in a: i = bisect_left(tails, x) if i == len(tails): tails.append(x) else: tails[i] = x return len(tails)

注意tails数组本身不是某个具体的子序列,它只是各个长度对应的最小结尾值,别拿它当答案输出。

区间DP的骨架是枚举区间长度、起点、分割点,三层循环:

for length in range(2, n + 1): for i in range(n - length + 1): j = i + length - 1 for k in range(i, j): dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + cost(i, j))

cost(i, j)是和并代价,比如石子合并里就是区间和,用前缀和预处理后 O(1) 拿到。这个模板几乎一字不改就能套用,属于性价比极高的一个。

5. 赛场策略与调试技巧

算法之外的功夫,往往决定了最终名次。这部分讲的是怎么把已有的水平稳定发挥出来。

5.1 暴力打表与对拍:拿分的底线

不是每道题都能想出正解。遇到卡住的题目,先写暴力拿部分分,这是个很实际的策略。暴力的写法要尽量简单,能给的分一定给满。

打表是一个被低估的技巧。如果题目问的是某个固定范围内的结果,而且数据规模不大,可以直接在本地跑一遍暴力把所有答案算出来,然后硬编码到提交的代码里。比如求一百万以内的某种数的个数,本地跑几分钟出结果,提交时代码里只写一个字典或者列表,瞬间通过。用这个技巧要注意两点:表不能太大导致代码超长,以及确认题目没有多组查询且范围超出表。

对拍是验证自己思路的利器。写一个暴力版本和一个优化版本,用随机数据生成器造大量小数据,两个程序分别跑,比较输出。如果几百组数据都一致,基本可以放心提交。我准备的一个简易对拍脚本长这样:

import random, subprocess for t in range(300): n = random.randint(1, 12) with open("in.txt", "w") as f: f.write(str(n) + "\n") f.write(" ".join(str(random.randint(1, 20)) for _ in range(n))) r1 = subprocess.run(["python", "brute.py"], stdin=open("in.txt"), capture_output=True, text=True).stdout.strip() r2 = subprocess.run(["python", "fast.py"], stdin=open("in.txt"), capture_output=True, text=True).stdout.strip() if r1 != r2: print("差异出现,输入:", open("in.txt").read()) print("暴力:", r1, " 优化:", r2) break

跑三百组小数据通常一分钟内能出结果,比手工造数据高效太多。

5.2 大数、精度与边界处理

Python的整数是任意精度的,题目里出现大数运算时不要习惯性地取模。但大整数的运算速度会随着位数增长而下降,一百位以内的数字运算还很快,几千位就会明显变慢。如果题目只需要最后几位,那就一边算一边取模。

浮点数方面,比较两个浮点数不要用==,改用差值绝对值小于一个很小的数,比如1e-9。输出保留小数位用格式化字符串:print(f"{ans:.2f}"),它会自动四舍五入,比自己写round更符合评测预期。注意round(2.675, 2)的结果可能是 2.67 而不是 2.68,因为二进制表示不精确,涉及金额类题目要格外小心。

边界处理是失分重灾区。空输入、只有一个元素、全部元素相同、最大最小值、负数下标,这几个场景我都会在心里过一遍。尤其是数组长度为1时的循环,range(1, n)直接不执行,有时候这是对的,有时候会导致答案错误,取决于题目是否要求输出单个元素本身。

提示:除法要分清//和int(a / b)。前者是向下取整,后者是先转浮点再截断,在负数上结果不同。比如-7 // 2是 -4,int(-7 / 2)是 -3。涉及负数取整的题目,这个差异会直接导致答案错误。

5.3 时间分配与提交顺序

我习惯的时间分配是这样的:前十分钟通读全部题目,标出哪些是明显会做的,哪些需要想一想,哪些完全没思路。然后按“会做且写得快”的顺序开做,每道题控制在十五到二十分钟内。做完一道立刻提交,不要攒着最后一起交,万一最后一分钟网络卡了,损失无法挽回。

填空题排在前面做,因为不需要考虑时间复杂度和边界,能算出来就行。编程题先做数据范围小的,因为范围小意味着暴力可能能过,拿分快。压轴题留到最后,如果只剩半小时还没思路,果断写暴力加特判,把能拿的部分分拿到手。

还有一个细节:代码提交前把调试用的print全部删掉或者注释掉。输出多余内容在大多数评测系统上会判定为格式错误,白丢一道题的分。我见过有人因为留了一行调试输出,从满分变成零分,这个教训值得记住。

6. 常见问题与排查速查表

把踩过的坑整理成表,赛前扫一遍,能省下大量现场排查时间。下面这些几乎都是我自己或者身边的人真实遇到过的。

6.1 运行时错误与答案错误排查

现象常见原因排查动作
RecursionError递归层数超过默认限制调大 setrecursionlimit,或改迭代写法
Runtime Error除零、下标越界、类型不匹配检查分母是否可能为0,下标范围,输入是否为空
Time Limit复杂度过高,或IO太慢换 bulk 读取,检查循环是否可降维
Memory Limit开了超大二维数组用一维滚动数组或 bytearray
Wrong Answer边界条件、取整方向、未清空全局状态手动跑小数据,检查多组数据间变量是否复位
输入读不完用了 input() 遇到空行中断改用 sys.stdin 逐行或整体读取
输出格式错行尾空格、多余换行、缺少分隔符对照题目输出样例逐字符比对

多组数据不清空全局变量这个问题特别隐蔽。比如并查集的parent数组、DP的状态数组,如果写在函数外层,上一组数据的残留会影响下一组。我的习惯是把所有状态定义在solve()函数内部,每次调用都是全新的,这样最省心。

另外,列表推导式里的变量作用域也容易出问题。Python 3 里推导式有独立作用域,外层同名变量不会泄漏,这个和 Python 2 不同,如果看的是老教程要注意。

6.2 赛前模板自检清单

比赛前一天,我会按这份清单把自己的模板库过一遍,确认每个模块都能独立跑通:

  • 环境层:sys.setrecursionlimit、sys.stdin.buffer.read()、sys.stdout.write三件套是否到位,版本号是否和评测机对齐。
  • 数学层:gcd、lcm、快速幂、线性筛、组合数,五个模板各跑一组样例。
  • 序列层:一维和二维前缀和、一维和二维差分、三个二分版本、离散化。
  • 搜索层:DFS回溯、BFS最短路、方向数组,确认回溯位置没写错。
  • 图论层:并查集迭代版、Dijkstra、Floyd、拓扑排序,各测一组。
  • DP层:01背包、完全背包、多重背包二进制拆分、最长递增子序列、区间DP。
  • 工具层:itertools.permutations、collections.Counter、deque、heapq、bisect、datetime,确认导入名没写错。

这份清单看起来长,实际过一遍不到一小时。我带过一个学妹,她赛前一天照着过了一遍,第二天比赛时遇到一道多重背包,直接套模板改了两行就过了,而旁边的人在手写二进制拆分时写错了下标。这就是准备的价值。

最后分享一个我自己用了很久的小技巧:把模板按功能拆成多个文件,文件名用中文,比如快速幂.py、并查集.py,比赛时新建文件直接复制内容。文件开头写一行注释说明这个模板的输入输出格式,比如“输入两个整数a b,输出a^b mod p,p需自行修改”。隔一个月再看也能三秒钟想起来怎么用,比什么都记在脑子里靠谱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询