ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

算法编程大赛历年试题:考点拆解、模板速记与四小时答题策略

算法编程大赛历年试题:考点拆解、模板速记与四小时答题策略

简介:面向准备百度之星等编程竞赛的学生与算法爱好者,这份历年试题归档把赛题按网页形式整理,可用于熟悉题型、专项训练和赛前查漏补缺。压缩包共116个文件,整体约983KB,包含62张题目配图、24个CSS样式文件、16个HTM页面、12个JS脚本及少量PNG、GIF素材,页面、样式与脚本相互配合,便于离线浏览和按主题检索。赛题内容覆盖基础编程、数组链表栈队列树图等数据结构、排序搜索动态规划贪心等算法,以及逻辑推理、数学应用和字符串处理,能够帮助读者理解竞赛命题思路并积累解题方法。已有636人学习,适合希望在C++、Java、Python等语言环境下提升问题建模与代码实现能力的读者作为练习资料。

1. 先别急着做压轴题:这场比赛的分数分散在不同题型里

很多人拿到算法编程大赛历年试题后第一件事,就是翻到最后一题,看自己离最难的那几题还差多远。做了两套下来,最容易丢的分数往往在前两题:题意已经读懂,也觉得自己会做,却因为边界没考虑清楚,或者多组数据没有清空全局变量,交了六七次也没过。这场面向高校学生的算法编程赛事通常四小时八道题,每题一百分,按通过测试点给分,前四题的分值加起来往往超过一半。签到、模拟、二分、动态规划(DP)、最短路、并查集这几类题目,只要板子(常用代码模板)写得熟、细节处理到位,就能稳定拿到分。这套历年试题适合已经能写完整程序、刷过一两百道题、但比赛名次总卡在中游的人:与其按难度顺序硬扛压轴题,不如按考点把历年题拆开重做一遍。

2. 考点地图:八道题的难度梯度与分值分布

历年题翻多了会发现一个规律:题号位置和考点类型高度相关。第 1、2 题几乎固定是模拟与简单数学,第 3、4 题集中在贪心、二分、前缀和与基础 DP,第 5、6 题开始上图论和数据结构,第 7、8 题才是思维题、复杂 DP 或需要组合多个算法的综合题。按这个梯度分配时间,比按题目顺序硬做更划算。下表把五类考点在历年题里的出场频次、常见分档和建议用时列了出来,可以当作刷题时的优先级参考。

考点类型出场频次常见题号位置建议用时目标
模拟与签到每场 2 题第 1、2 题25 分钟内一次通过,不交罚时
贪心、二分、前缀和每场 2 到 3 题第 3、4 题50 分钟至少通过一题,另一题拿到部分分
动态规划每场 1 到 2 题第 4 到第 6 题40 分钟想出状态定义,能写就能拿分
图论与并查集每场 1 到 2 题第 5、6 题45 分钟板子默写速度决定成败
数学、字符串、综合每场 1 到 2 题第 7、8 题剩余时间能推就推,推不出就抓部分分

2.1 签到题与模拟题:前二十分钟能决定后面怎么做

签到题的特征很稳定:题意直白,数据范围小,样例给得全,不需要任何算法,只需要把规则翻译成代码。历年题里反复出现的有三类:日期与时间换算(给起止时间求跨越天数、按整点分桶统计)、矩阵与坐标变换(旋转、螺旋遍历、按对角线输出)、规则模拟(排队、计分、消除)。看起来简单,但这类题的通过率并没有想象中高,因为它考的是细节:闰年规则、跨年跨月、下标从零开始还是从一开始、输出格式要不要保留小数位。

看一道典型变形:“给定若干个闭区间,求被覆盖的总长度,重叠部分只算一次”。这题本质是区间合并,思路是先把所有区间按左端点排序,然后线性扫描,维护当前合并段的右端点cur_r:如果下一个区间的左端点小于等于cur_r,说明有重叠,把cur_r更新为两者较大值;否则结算当前段长度并开启新段。面试和笔试里它常被包装成“会议安排”“日程冲突检测”“统计在线时长”,考点完全一样。写的时候容易忽略的是区间端点是否包含、输入是否保证左端点小于右端点、以及所有区间都不重叠时循环结束后还要补一次结算。

提示:签到题不要一上来就想着优化。先写最直白的版本,样例过了再考虑数据范围是否需要改算法,很多翻车都是败在“还没通过样例就开始重构”。

2.2 动态规划:从一维到树形的三个台阶

DP 在历年题里几乎每场都有,难度跨度也最大,可以分成三个台阶。第一个台阶是一维线性 DP:最大子段和、最长上升子序列、打家劫舍这类,状态只有一个维度,转移只依赖前一个或前几个位置。第二个台阶是二维 DP:01 背包、完全背包、区间 DP(石子合并、括号匹配、回文分割),状态有两个维度,写的时候要特别注意遍历顺序,背包问题里容量维必须倒着遍历才能保证每个物品只用一次。第三个台阶是树上 DP 与状压 DP:最大独立集、换根求最远距离、以位掩码表示集合的 TSP 变种。

答题时的固定顺序是:先用一句话说清状态含义(“dp[i]表示以 i 结尾的最大子段和”),再写转移方程,最后处理初始化。三步里最容易被跳过的是初始化,而它恰恰是失分重灾区。比如“恰好装满容量为 V 的背包”这类题,初始化时除了dp[0] = 0,其余必须是负无穷而不是 0,否则“未装满”的状态会被当成合法解传递下去。

历年题里有一道很能区分水平的分割问题:“把长度为 n 的数组分成 k 段连续子数组,使各段和的最大值最小”。这题有两个方向,一是二分答案加贪心判定,二是区间 DP(复杂度 O(n²k),只能过小数据)。考场上先看数据范围:n 上千、k 较小就走二分答案;n 只有几百就走 DP。同一个人在同一场比赛里能不能根据数据范围切换算法,比会不会写 DP 更能体现经验。

2.3 图论三件套:最短路、最小生成树、拓扑排序

图论题的分值通常偏高,但板子极其固定,属于“背下来就能拿分”的模块。最短路要准备三份:堆优化的 Dijkstra(非负权单源)、Floyd(n 在 500 以内、需要全源)、Bellman-Ford 或队列优化的版本(带负权时用,但要能判断负环)。最小生成树准备 Kruskal 加并查集就够,稠密图上 Prim 更优但很少考。拓扑排序既是判环工具,也是 DAG 上做 DP 的前置步骤。这三个板子加起来不到一百行,背熟之后省下的时间可以全部投给后面的题。

变形题才是真正拉开差距的地方。历年题里常见三种:一是多源最短路,做法是建一个超级源点,把它到所有起点的边权设为 0,然后跑一次单源最短路;二是分层图,题目说“最多可以免费经过 k 条边”,把每个点拆成 k+1 层,免费边从第 i 层连到第 i+1 层且边权为 0,答案取各层终点的最小值;三是双关键词最短路,“在距离最短的前提下花费最少”,用距离作为堆比较的第一关键字、花费作为第二关键字即可,不需要写两遍。

写图论题最容易出的问题不在算法,而在建图:重边要取最小值、自环要直接忽略、无向图数组要开两倍、节点编号是 0 起始还是 1 起始要统一,还有不能到达时的输出约定。这些细节在样例里通常不会暴露,交上去才会挂,属于典型的血泪经验。

2.4 二分、前缀和与数据结构:模板的熟练度直接决定速度

二分答案的识别信号是“最大化最小值”“最小化最大值”“求第 k 大”这类措辞。判定函数必须满足单调性:答案成立时,比它更宽松(或更严格)的一侧也全部成立。写二分的模板要固定下来,避免在边界上反复试,常用的是左闭右开写法:while lo < hi: mid = (lo + hi) // 2,根据判定结果移动lo或hi,循环结束时lo就是答案。真正容易错的是判定函数内部的贪心策略,二分本身反而简单。

前缀和与差分是降维工具。一维前缀和把区间求和从 O(n) 降到 O(1),二维前缀和用于子矩阵求和,差分把“区间加、最后统一查询”从 O(nm) 降到 O(n+m)。历年题里非常常见的“多次区间修改,最后输出整个数组”就是差分模板题,写起来只有四行。

数据结构方面,优先级从高到低是:单调栈(下一个更大元素、柱状图最大矩形)、单调队列(滑动窗口最值)、树状数组(单点修改区间查询、逆序对计数)、线段树(区间修改区间查询)。线段树代码长、出错率高,赛场上除非确定要用,否则优先考虑树状数组或者离线做法。判断标准很简单:如果操作可以离线并且能用排序加树状数组解决,就不要写线段树。

2.5 数学与字符串:真正决定名次的分水岭

数学题在前四题里也有,但通常是浅层的:gcd 与 lcm、快速幂取模、组合数取模、素数筛、进制转换。这些必须做到默写级别,尤其是快速幂和组合数的预处理,很多题只差这一步就能从部分分变成满分。稍微深一点的是扩展欧几里得解同余方程、容斥原理计数,出现频率不高,但一旦出现,会的人十分钟写完,不会的人一百分全丢。

字符串题的主力是 KMP、字符串哈希和字典序比较。KMP 的next数组除了做匹配,还有一个使用频率很高的用途:求最短循环节。若长度为 n 的串由某个前缀重复若干次构成,最短循环节长度是n - next[n],且当n % (n - next[n]) == 0时串是周期串。这个结论在历年题里至少出现过三次不同包装的版本,值得直接记住。字符串哈希则用来做多次子串相等判断,注意双模数或使用 64 位无符号自然溢出,单模数容易被构造数据命中。

3. 高频真题:六类反复出场的题型与答题思路

下面这六类题在历年试题里出现的频率极高,几乎每年都能找到对应版本。每一类给出一份可直接默写的实现,重点不在代码本身,而在于它对应的识别信号和变形方向。

3.1 差分:区间修改的通用解法

题面通常是这样:“有 n 个位置初始值为 0,进行 m 次操作,每次给区间 [l, r] 加上 v,最后输出每个位置的值”。朴素做法是每次循环,复杂度 O(nm),数据一大就会超时。差分数组d[i] = a[i] - a[i-1],对区间加 v 只需要d[l] += v和d[r+1] -= v两次操作,最后做一次前缀和就还原出原数组。

# 差分数组:区间加、最后统一查询 n, m = map(int, input().split()) d = [0] * (n + 2) # 多开两位,防止 r+1 越界 for _ in range(m): l, r, v = map(int, input().split()) d[l] += v # 区间起点加 v d[r + 1] -= v # 区间终点后一位减 v,抵消影响 cur = 0 res = [] for i in range(1, n + 1): cur += d[i] # 前缀和还原 res.append(cur) print(*res)

d数组开n+2是为了让r+1在r == n时也不越界,这一位之后不会被输出,所以不用清理。若题目是“区间加、区间求和”,差分就要升级成树状数组维护两个差分数组,属于中等难度题型。

注意:差分处理的是“先全部修改、最后统一查询”的场景。如果修改和查询交替出现,就必须上树状数组或线段树,用差分只会得到错误答案。

3.2 01 背包:三种常见问法的统一写法

背包题的三种问法要分清:求最大价值、求恰好装满时的最大价值、求方案数。它们的转移方程骨架相同,差别只在初始化。一维滚动数组的标准写法是容量维倒序遍历,这样dp[j]在被更新时依赖的dp[j-w]还是上一轮的值,等价于每个物品只用一次。

# 01 背包:n 件物品,容量 W,求最大价值 n, W = map(int, input().split()) w = [0] * n v = [0] * n for i in range(n): w[i], v[i] = map(int, input().split()) dp = [0] * (W + 1) # 求最大价值时全部初始化为 0 for i in range(n): for j in range(W, w[i] - 1, -1): # 倒序遍历,保证每件物品只用一次 dp[j] = max(dp[j], dp[j - w[i]] + v[i]) print(dp[W])

如果题目改成“恰好装满”,初始化就要换成dp[0] = 0、其余为负无穷,最后dp[W]若仍是负无穷说明无解。如果改成“求方案数”,转移从取 max 变成累加,容量维依然倒序。如果改成完全背包(每件物品无限件),只要把容量维改成正序遍历即可。这三种变形靠改一行就能完成,前提是理解倒序和正序背后的含义,而不是死记。

3.3 分层图最短路:k 次免费机会的标准处理方式

题面:“给定一张带权无向图,从 s 到 t,最多可以让 k 条边的权值变为 0,求最短距离”。这类题如果不熟悉套路,很容易写成搜索加剪枝,最后超时。标准做法是把状态扩展成(节点, 已用免费次数),共 n×(k+1) 个状态,跑一次 Dijkstra。实际实现时不一定真去建分层图,直接在 dist 数组上多开一维即可。

import heapq # dist[u][c]:到达 u、已用掉 c 次免费机会时的最短距离 def solve(n, adj, s, t, k): INF = float('inf') dist = [[INF] * (k + 1) for _ in range(n + 1)] dist[s][0] = 0 pq = [(0, s, 0)] # (距离, 节点, 已用次数) while pq: d, u, c = heapq.heappop(pq) if d > dist[u][c]: continue for v, w in adj[u]: if d + w < dist[v][c]: # 不使用免费机会 dist[v][c] = d + w heapq.heappush(pq, (d + w, v, c)) if c < k and d < dist[v][c + 1]: # 用掉一次免费机会,边权变 0 dist[v][c + 1] = d heapq.heappush(pq, (d, v, c + 1)) return min(dist[t]) # 取终点各层的最小值

答案要取min(dist[t])而不是dist[t][k],因为“最多 k 次”意味着用不满也合法。这个模板稍加改动就能应付另外两种常见题:把边权换成“通过某类边的次数”“换乘次数”“状态开关”,都只是状态维度上的加法。

3.4 二分答案:把最优化问题变成判定问题

识别信号是题目里出现“最大值最小”“最小值最大”“第 k 小”“能否在 X 之内完成”。写法固定:外层二分答案,内层写一个贪心判定函数。以“把数组分成 k 段连续子数组,使最大段和最小”为例,判定函数是:从左往右累加,一旦超过 limit 就另起一段,最后统计段数是否不超过 k。

def can_split(nums, k, limit): cnt, cur = 1, 0 for x in nums: if x > limit: # 单个元素就超限,直接不可行 return False if cur + x > limit: cnt += 1 cur = x else: cur += x return cnt <= k def min_max_sum(nums, k): lo, hi = max(nums), sum(nums) # 答案下界是最大元素,上界是总和 while lo < hi: mid = (lo + hi) // 2 if can_split(nums, k, mid): hi = mid # 可行,尝试更小 else: lo = mid + 1 # 不可行,必须放大 return lo

下界取max(nums)而不是 0,可以少跑几轮;这一细节在时间紧张时确实能省下不少时间。判定函数内部一旦发现x > limit就直接返回 False,避免后续无意义的计算。保存答案的变量要不要单独开,取决于你用哪种二分写法,左闭右开写法不需要额外变量,循环结束的lo就是答案。

3.5 并查集:连通块与“朋友圈”类问题

并查集的出现频率高得惊人,而且经常以伪装形式出现:“判断两个人是否在同一个圈子”“统计图中连通块数量”“按时间顺序加边,问最早何时全部连通”。核心只有两个操作:查找根节点(带路径压缩)、合并两个集合(按秩或按大小合并)。加上一个 size 数组,就能顺便回答“最大连通块有多大”。

parent = list(range(n + 1)) size = [1] * (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: return False # 已在同一集合,可用于判环 if size[ra] < size[rb]: ra, rb = rb, ra parent[rb] = ra size[ra] += size[rb] return True

union返回 False 说明这条边连接的两个点已经连通,加上它就成环,这是 Kruskal 判环和“冗余连接”类题目的关键。用并查集统计连通块数量时,不要每次都遍历一遍数根,而是在合并成功后把计数减一,初始计数为 n,边界处理更清晰。

3.6 单调栈与单调队列:区间最值的降维技巧

单调栈解决的是“左边或右边第一个比它大、比它小的元素”,单调队列解决的是“滑动窗口内的最值”。两者的共同点是用一个栈或双端队列维护候选集合,把不可能成为答案的元素及时丢掉,复杂度从 O(n²) 降到 O(n)。柱状图中的最大矩形、接雨水、窗口最大值这几道经典题,全是从这两个模板长出来的。

from collections import deque # 滑动窗口最大值:维护一个下标队列,对应的值单调递减 def max_sliding_window(nums, k): dq = deque() res = [] for i, x in enumerate(nums): while dq and nums[dq[-1]] <= x: dq.pop() # 队尾元素比当前值小,永远不可能成为最大值 dq.append(i) if dq[0] <= i - k: dq.popleft() # 队首滑出窗口 if i >= k - 1: res.append(nums[dq[0]]) return res

队列里存下标而不是值,是为了判断队首是否已经滑出窗口;这个细节写错的话,样例能过、大数据会挂。单调栈的写法与之类似,只是弹出条件换成“栈顶元素小于当前值”,弹出时即可计算栈顶元素的左右边界。

4. 真题实战:三道完整题的读题到 AC 全过程

会默写模板,不等于能在赛场上把题做出来。下面三题覆盖了搜索、构造、树上查询三个方向,重点看的是从读题到确定算法的判断过程,而不是代码有多长。

4.1 网格最大连通块:搜索题的通用骨架

题面:“给定一个 n×m 的网格,1 表示陆地,0 表示水,求最大的四连通陆地面积”。这类题几乎是搜索类题目的固定模式,变体包括八连通、统计连通块数量、判断是否被水完全包围。要考虑的关键点有三个:是否需要额外标记数组来避免重复访问、是否需要处理边界、递归是否会因为深度过大而爆栈。

import sys from collections import deque def max_area(grid): n, m = len(grid), len(grid[0]) seen = [[False] * m for _ in range(n)] best = 0 for i in range(n): for j in range(m): if grid[i][j] == 1 and not seen[i][j]: seen[i][j] = True q = deque([(i, j)]) area = 0 while q: x, y = q.popleft() area += 1 for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): nx, ny = x + dx, y + dy if 0 <= nx < n and 0 <= ny < m and grid[nx][ny] == 1 and not seen[nx][ny]: seen[nx][ny] = True q.append((nx, ny)) best = max(best, area) return best

这里用 BFS 而不是 DFS,理由是网格规模通常到达 1000×1000,递归写法需要把递归深度设置调大,而设置递归深度本身有风险,还容易拖慢速度。seen在入队时就置为 True,而不是出队时,否则同一个格子可能被重复入队,队列会变大很多。这套骨架换成八连通只需要改动方向数组,换成统计数量只需要把best = max(...)改成累加。

4.2 字典序第 k 小:逐位确定法

题面:“求 1 到 n 中字典序第 k 小的数”。把数字按字典序排好后,它其实构成了一棵十叉树:根是空,第一层是 1 到 9,每个节点下面挂十个子节点。求第 k 小,等价于在这棵树上做一次带计数的遍历:判断以当前前缀为根的子树有多少个节点,如果这个数量小于 k,就直接跳过整棵子树;否则进入下一层。

def count_prefix(prefix, n): # 统计以 prefix 为前缀、且不超过 n 的数字个数 cnt, first, last = 0, prefix, prefix while first <= n: cnt += min(last, n) - first + 1 first *= 10 last = last * 10 + 9 return cnt def kth_number(n, k): cur = 1 k -= 1 # 第一个数是 1,先消耗掉 while k > 0: cnt = count_prefix(cur, n) if cnt <= k: k -= cnt # 整棵子树都不够,跳过 cur += 1 else: cur *= 10 # 进入子树的第一层 k -= 1 return cur

count_prefix里用first和last两个变量按层数计算区间长度,避免了逐个数枚举;这是这道题能否通过大范围数据的关键。整体复杂度是 O(log n × log n),n 到十的九次方也毫无压力。

4.3 树上路径查询:倍增求 LCA

题面:“给一棵 n 个节点的树,m 次询问,每次问两个节点之间路径上的第 k 个点”或者“求两点距离”。这类题的共同前置是最近公共祖先(LCA)。倍增法最实用:预处理每个节点向上 2^j 步的祖先,查询时先把深度大的那个节点向上提到同一层,再同时向上跳。

import sys sys.setrecursionlimit(300000) def build(n, adj): LOG = max(1, n.bit_length()) up = [[0] * (n + 1) for _ in range(LOG)] depth = [0] * (n + 1) stack = [1] visited = [False] * (n + 1) visited[1] = True while stack: # 迭代深搜,避免递归过深 u = stack.pop() for v in adj[u]: if not visited[v]: visited[v] = True depth[v] = depth[u] + 1 up[0][v] = u stack.append(v) for j in range(1, LOG): for i in range(1, n + 1): up[j][i] = up[j - 1][up[j - 1][i]] return up, depth, LOG def lca(u, v, up, depth, LOG): if depth[u] < depth[v]: u, v = v, u diff = depth[u] - depth[v] for j in range(LOG): if diff >> j & 1: u = up[j][u] # 把较深的节点提到同一层 if u == v: return u for j in range(LOG - 1, -1, -1): if up[j][u] != up[j][v]: u, v = up[j][u], up[j][v] # 同步上跳,直到父节点相同 return up[0][u]

建表时用迭代代替递归,是为了避开深度一万的链状树。第二次跳出循环时返回的是up[0][u],也就是两个节点共同父节点,而不是u本身,这一步写反了就必然出错。求出 LCA 后,两点距离是depth[u] + depth[v] - 2 * depth[lca],路径第 k 个点则先判断 k 小于等于深度差的一半还是位于另一侧,按方向逐个上跳。

5. 易错点:五条最容易丢分的地方

5.1 整型溢出:数据范围没读全

错在哪:看到“答案不超过十的九次方”就直接开 int,中间计算两个大数相加或者相乘时溢出,样例全过、提交全挂。

为什么错:题目给的是最终答案的范围,不是中间过程的取值范围。两个接近十的九次方的数相加,中间结果就已经超了;涉及乘法时更严重。

怎么答对:读题时先看数据范围,把所有可能超过十的九次方的变量统一换成 64 位整数,尤其在 Python 里要注意的是浮点数精度而不是整数,但在需要输出整数时不要中途做除法,改成先乘后除或直接整数除。

5.2 多组输入没清空:一组对、多组就错

错在哪:题目给 T 组数据,第一组正确,后面几组全错,或者答案逐渐累加。

为什么错:全局数组、列表、计数器在每组数据之间没有重置,上一组的结果混进来了。

怎么答对:固定写成“每组数据处理前重置”的结构,把需要清空的变量集中放在循环开头;对列表用重新赋值而不是修改元素,不要在大数组上做切片清空,那样会浪费时间。

5.3 浮点比较:等于号不能直接用

错在哪:二分里写if ans == mid退出循环,或者几何题里用==判断共线,结果陷入死循环或者误判。

为什么错:浮点数在计算机里是近似的,两个数学上相等的值,二进制表示可能有极小差异。

怎么答对:二分固定迭代次数(比如一百次)而不是靠相等退出;比较用abs(a - b) < 1e-9;能转成整数运算就转,比如把斜率比较改成乘法交叉比较,把距离比较改成平方比较。

5.4 递归深度:一条链就让程序崩溃

错在哪:树上问题用递归写,n 到十万时提交直接出现运行错误,样例却完全正常。

为什么错:链状树的递归深度等于节点数,超过了默认的栈空间。

怎么答对:两种方案,一是把递归改成显式栈的迭代写法,二是设置递归深度上限。前者更稳,尤其在评测环境的栈大小不确定时;后者虽然一行搞定,但深度特别大时仍可能失败。

5.5 特判缺失:n 等于一、空集、重边自环

错在哪:交上去挂了几个测试点,一查都是极小规模数据。

为什么错:常规逻辑在规模为 1、答案为空、图上有重边或自环时会走到特殊分支。

怎么答对:写完主体逻辑后,固定用几组小数据自查:n 等于 1、n 等于 2、全部元素相同、没有任何满足条件的答案、图只有一个点和一条自环边。这五组能覆盖绝大多数边界情况,花五分钟换几十分。

6. 答题技巧:四小时的时间分配方案

赛场上最亏的不是不会,而是会做却没时间写。给出一套经过多次实赛验证的分配方案:前 25 分钟扫一遍全部题目,边看边把每题按“会写、有思路、完全没思路”分三档,并记下每道题的数据范围和样例;接下来 40 分钟专攻两题签到题,要求一次通过;然后 60 分钟处理有思路的中档题,优先选分值高、代码短的;剩余时间先给“有思路但没写完”的题补完,再考虑压轴题的部分分。

时间段任务通过标准
0 到 25 分钟通读题目,标记难度与考点每题的算法方向基本确定
25 到 65 分钟两道签到题一次通过,不反复提交
65 到 125 分钟一到两道中档题至少一道通过,另一道拿到部分分
125 到 180 分钟攻克思路清晰的难题通过大范围数据的测试点
180 到 240 分钟检查边界、补部分分提交前过一遍自测清单

两处容易被忽略的动作:一是提交前的自测清单,写成一个固定流程,包含“边界数据、多组数据、极大数据、输出格式”四项,逐项检查完才交;二是把每道题的算法思路用一行注释写在文件顶部,中途换题回来时不用重新读代码。下面这段读取模板建议直接存进自己的代码库,遇到输入量大时能省不少时间。

import sys def main(): data = sys.stdin.buffer.read().split() idx = 0 t = int(data[idx]); idx += 1 out = [] for _ in range(t): n = int(data[idx]); idx += 1 # 业务逻辑写在这里,out 里累积答案 out.append(str(n)) sys.stdout.write("\n".join(out)) main()

用sys.stdin.buffer.read()一次性读入全部输入再切分,比逐行读取快得多,尤其是输入里有十万级别整数时差距明显;out列表累积后一次性输出,避免频繁调用输出函数。注意切分后所有元素都是字节串,要显式转成整数。

最后说一句个人教训:早年我在一场比赛里把会做的第三题拖到最后写,结果时间不够,只拿到签到题的分,排名直接从中间掉到末尾。从那以后我每次开赛都强制走一遍“先扫题、再定档、按档分配时间”的流程,写完一题立刻用边界数据自测,再决定要不要提交。这个习惯比多背三个算法模板更能提高成绩。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进