Hello, ACM-ICPC
国际大学生程序设计竞赛
ACM-ICPC 起源于 1970 年,由 ACM(国际计算机学会)主办,是历史最悠久、影响力最大的大学生程序设计竞赛。 这不是考试,而是一场团队竞技:3 人一队、共用一台电脑、5 小时, 解决 7~13 道英文算法题。本页按 「认知 → 语言 → 算法 → 实战」的递进路线组织, 帮助零基础的你一步步走上区域赛领奖台。
排名规则:过题数量优先,数量相同比总用时;每次错误提交罚时 20 分钟,未通过的题目不计时。 配套三大学习网站:Codeforces · 洛谷 · OI Wiki(详见第 11 章)。
OJ 与评测系统
新手第一课 · 必懂Online Judge(OJ)是为程序设计竞赛而生的自动判题系统:提交源代码(C/C++/Java/Python 等), 系统编译执行,用出题人预设的测试数据判断正确性,同时有严格的时间与内存限制。
一道 OJ 题目的结构
评测结果的 7 种情况(必背)
| 徽章 | 全称 | 含义 |
|---|---|---|
| AC | Accepted | 题目通过 ✓ 恭喜! |
| WA | Wrong Answer | 答案错误,全部或部分输入没有得到预期输出 |
| RTE | Run Time Error | 运行出错、意外终止——常见于数组越界、除 0、爆栈 |
| TLE | Time Limit Exceeded | 运行超时——多半是算法复杂度不对,先想复杂度再改代码 |
| PE | Presentation Error | 输出格式错(多了空格、少了换行)——距离 AC 只差一步 |
| MLE | Memory Limit Exceeded | 内存溢出 |
| CE | Compile Error | 编译错误——本地先编译一遍再提交 |
Codeforces(CF)生态速览
- Pretest → System Test:赛中只过部分测试点(Pretest Passed),赛后统一全量测评,没过就是 FST
- Hack 机制:Lock 自己的代码后可看同 Room 选手代码,构造数据卡掉对方:成功 +100,失败 −50
- GYM:还原真实 ACM-ICPC 规则的训练场,可组队虚拟参赛(Virtual Participation)
- Problemset:按标签(dp / graphs / dsu…)和难度筛选题目,适合专题训练
| Rating 区间 | 头衔 | 颜色 |
|---|---|---|
| [2600, ∞) | International Grandmaster | 红 |
| [2200, 2600) | Grandmaster | 红 |
| [2050, 2200) | International Master | 黄 |
| [1900, 2050) | Master | 黄 |
| [1700, 1900) | Candidate Master | 紫 |
| [1500, 1700) | Expert | 蓝 |
| [1350, 1500) | Specialist | 绿 |
| [1200, 1350) | Pupil | 绿 |
| (−∞, 1200) | Newbie | 灰 |
语言基础与复杂度
阶段一 · ★★★★★竞赛主流语言是 C++。先掌握语法与 STL,再建立「先估复杂度、再写代码」的习惯——这是所有算法的地基。
竞赛通用模板(背下来)
C++#include <bits/stdc++.h> // 万能头(绝大多数 OJ 支持) using namespace std; int main() { ios::sync_with_stdio(false); // 关闭同步,cin/cout 提速 cin.tie(nullptr); int n; cin >> n; vector<int> a(n); for (auto &x : a) cin >> x; sort(a.begin(), a.end()); cout << *max_element(a.begin(), a.end()) << "\n"; return 0; }
while (scanf("%d", &n) != EOF) 或 while (~scanf("%d", &n))——EOF 即 −1,取反后为 0 使循环退出。STL 必会清单
vector动态数组 /sort排序pair二元组 /map · set(红黑树)priority_queue堆 /queue · stack
string/lower_bound · upper_bound__builtin_popcount二进制 1 的个数- 注意:
map有序、复杂度 O(log n)
时间复杂度速查(1 秒 ≈ 108 次运算)
| n 的规模 | 可行复杂度 | 典型算法 |
|---|---|---|
| n ≤ 12 | O(n!) | 全排列暴力搜索 |
| n ≤ 25 | O(2n) | 子集枚举、状压搜索 |
| n ≤ 5000 | O(n²) | 朴素 DP、双重循环 |
| n ≤ 105 | O(n log n) | 排序、二分、线段树 |
| n ≤ 107 | O(n) | 线性扫描、双指针 |
| n ≥ 109 | O(log n) / O(1) | 数学公式、快速幂 |
部分排序算法复杂度(经典表)
| 排序方法 | 最好时间 | 平均时间 | 最坏时间 | 辅助空间 | 稳定性 |
|---|---|---|---|---|---|
| 直接插入 | O(n) | O(n²) | O(n²) | O(1) | 稳定 |
| 二分插入 | O(n) | O(n²) | O(n²) | O(1) | 稳定 |
| 希尔 | — | O(n1.25) | — | O(1) | 不稳定 |
| 冒泡 | O(n) | O(n²) | O(n²) | O(1) | 稳定 |
| 快速 | O(n log n) | O(n log n) | O(n²) | O(log n) | 不稳定 |
| 直接选择 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
| 堆 | O(n log n) | O(n log n) | O(n log n) | — | 不稳定 |
亲手试一试 · 复杂度体检器
竞赛解题第一步:先看数据范围,再定算法复杂度。看到 n ≤ 10⁶ 还想写双重循环?TLE 会教育你的。
递进式学习路线
本站核心 · 按图索骥学习算法是一个循序渐进的过程。不要指望一口气啃完《算法导论》再做题——看书与刷题应当是螺旋式推进的。 下面是为零基础新手设计的四阶段路线,每阶段都有明确目标。
| 阶段 | 学什么 | 练什么 | 达成标志 |
|---|---|---|---|
| 一 语言基础 ~2 周 | C++ 语法、STL 容器、调试技巧 | 模拟 / 水题 50 道以上 | 能独立 AC 简单题 |
| 二 入门算法 ~4 周 | 枚举、模拟、排序、二分、贪心、简单数学 | CF Div.2 A/B、洛谷入门组 | 现场赛第一题稳定拿下 |
| 三 核心算法 ~8 周 | 搜索、DP、基础数据结构、图论 | CF Div.2 C/D、往届区域赛真题 | 区域赛铜牌区 |
| 四 专题进阶 持续 | 网络流、数论、字符串、计算几何、树上问题 | 真题套题 + 虚拟参赛 + 补题 | 银牌 / 金牌区 |
螺旋式学习法(看书 ↔ 刷题)
一周计划示例(参考小明的故事)
- 周一 ~ 周五:每天固定时间做 1 道题(工作日做题)
- 周六 ~ 周日:看《算法竞赛入门经典》等书,梳理本周知识点
- 组队后:每周一场组队训练(区域赛真题虚拟参赛)+ 赛后讲题补题
- 执行策略:队友之间互相监督打卡——「你今天做题了吗?没有?还不赶紧去做!」
模拟与枚举
阶段二 · 入门必经模拟题是按照题目要求操作即可得到结果的一类题目,注重考查代码实现能力、细节和特殊情况处理。 区域赛第一题(签水题)几乎都是模拟。
实战示范:股票交易(入门经典题)
题面 · 样例
题意:给定连续时间点的股价数据,寻找一次买卖股票的每股最大收益(先买后卖);无论如何无法取得收益则输出 No solution。
输入:多组测试数据(约 10 组),以 EOF 结尾。第一行为 n(0 < n ≤ 1 000 000);第二行为 n 个 int 范围内的正整数。
样例:5 / 1 2 3 4 2 → 3(第 1 分钟 1 买、第 4 分钟 4 卖);2 / 2 2 → No solution。
三种解法的演进(思考过程比答案更重要)
| 方法 | 思路 | 复杂度 | 结论 |
|---|---|---|---|
| 暴力求解 | 枚举每对买/卖日期组合,取最大收益 | Ω(n²) | n = 10⁶ 时必超时,不可行 |
| 分治 | 转为「最大子数组」问题:递归左半、右半、跨越中点三种情况 | O(n log n) | 可行,代码较长 |
| 线性扫描 | 从前往后扫,维护前 i−1 项最小值 x,ans = max(ans, a[i] − x) | O(n) | 最优解,代码极短 |
C++int main() { int n; while (~scanf("%d", &n)) { /* 多组数据,EOF 结尾 */ int res, ans = 0, x; scanf("%d", &x); res = x; /* res 记录前缀最小值 */ for (int i = 2; i <= n; i++) { scanf("%d", &x); if (ans < x - res) ans = x - res; /* 更新最大收益 */ if (x < res) res = x; /* 更新最小值 */ } if (ans == 0) printf("No solution\n"); else printf("%d\n", ans); } return 0; }
经典陷阱:数据溢出(2017 沈阳站 I 题 Little Boxes)
C++/* a,b,c,d ≤ 2^62,四数之和最大可达 2^64,unsigned long long 也装不下 */ if (a == (1ULL << 62) && b == (1ULL << 62) && c == (1ULL << 62) && d == (1ULL << 62)) printf("18446744073709551616\n"); /* 特判输出 2^64 的真实值 */ else printf("%llu\n", a + b + c + d);
搜索
阶段三 · 核心算法搜索用于枚举所有情况或遍历图中每个点,适用于数据范围较小的场景。核心两板斧: 深度优先搜索(DFS,递归实现)与广度优先搜索(BFS,借助队列,求最少步数)。
DFS / BFS 对比与模板
| DFS 深度优先 | BFS 广度优先 | |
|---|---|---|
| 实现 | 递归 / 栈 | 队列 |
| 适合 | 枚举所有方案、连通性、树上问题 | 最少步数、层序扩展 |
| 去重 | vis 数组 / set 记录访问状态 | 入队时立即标记 |
C++/* DFS:递归遍历 */ void dfs(int u) { vis[u] = true; for (int v : g[u]) if (!vis[v]) dfs(v); } /* BFS:队列 + 最少步数 */ queue<int> q; q.push(s); vis[s] = true; while (!q.empty()) { int u = q.front(); q.pop(); for (int v : g[u]) if (!vis[v]) { vis[v] = true; step[v] = step[u] + 1; q.push(v); } }
剪枝:搜索不超时的关键
- 可行性剪枝:到达某状态时,无论后面怎么做都达不到题目条件,立即回溯
- 最优性剪枝:若当前可能的最高得分都达不到已有最优解,直接停止搜索
- 状态保存与恢复:回溯前先备份地图/状态,递归返回后恢复——小心全局变量被污染
例题:Counting Cliques(2016 沈阳站 E)· 统计大小为 S 的团
思路:数据范围小(N ≤ 100,M ≤ 1000,S ≤ 10),从每个点 DFS。加一个新点前先检查它与当前团内所有点都有边;搜完一个点要把它重新标记为未使用,避免遗漏。
技巧:邻接表只加单向边避免同一个团被重复统计;同时用邻接矩阵 e[u][v] 实现 O(1) 判边。多组数据记得清零。
动态规划
阶段三 · 核心算法动态规划(DP)是一种决策过程:把多阶段问题拆成很多单阶段,利用阶段之间的关系逐个求解。 重点在于找到状态转移方程——可能从前向后、区间从小到大、沿轮廓线、或跟数位相关,变化多端。
经典模型清单(按序攻克)
- 线性 DP:LIS 最长上升子序列、LCS
- 背包:01 / 完全 / 多重
- 区间 DP:石子合并类
- 树形 DP / 数位 DP(记忆化搜索)
- 轮廓线 DP(状态压缩)
- 递推 vs 记忆化搜索两种写法
区间 DP 示例:石子合并(朴素版)
C++/* dp[i][j] = 合并区间 [i, j] 的最小代价;大区间由小区间推出 */ for (int len = 2; len <= n; len++) for (int i = 1; i + len - 1 <= n; i++) { int j = i + len - 1; dp[i][j] = INF; for (int k = i; k < j; k++) dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum(i, j)); }
真题状态设计对照
| 真题 | DP 类型 | 状态定义 |
|---|---|---|
| Pangu and Stones(2017 北京 J) | 区间 DP | dp[i][j][k]:区间 [i, j] 分成 k 堆的最小代价;每次只能合并连续的 L~R 堆,答案 dp[1][N][1] |
| 划分型 DP(2021 回忆版改编) | 二分答案 + 贪心判定 | 答案单调 → 二分 x,判定「能否分成 ≤ m 段且每段和 ≤ x」 |
| TSP(2024 题库精选改编) | 状压 DP | dp[S][i]:已访问集合 S、当前在 i 的最短路径,O(n²·2ⁿ) |
数据结构
阶段三 · 核心算法当暴力求解复杂度偏高时,可根据问题中的限定(如区间、前缀、最大异或)使用相应数据结构维护相应的值,达到优化时间复杂度的目的。
常用数据结构速查
| 结构 | 典型用途 | 复杂度 |
|---|---|---|
| 栈 / 队列 | 括号匹配、模拟过程 | O(1) 单步 |
| 优先队列(堆) | 按时间取最小/最大事件、哈夫曼式合并 | O(log n) |
| 并查集 | 连通性、合并集合 | 近似 O(α) |
| 树状数组 | 单点修改 + 区间求和、逆序对、离线查询 | O(log n) |
| 线段树 | 区间修改(lazy)+ 区间最值/求和、扫描线 | O(log n) |
| 字典树 Trie | 字符串前缀、01 字典树求最大异或 | O(位数) |
树状数组模板(lowbit 是灵魂)
C++int lowbit(int x) { return x & (-x); } void update(int x, int d) { while (x <= n) { BIT[x] += d; x += lowbit(x); } } int query(int x) { int ret = 0; while (x) { ret += BIT[x]; x -= lowbit(x); } return ret; } /* 注意:树状数组下标从 1 开始,不能出现 0,否则 lowbit 会出问题 */
01 字典树求最大异或(经典应用)
C++/* 把每个数按二进制从高位到低位插入字典树; 查询与 x 异或最大的值:每一位尽量走【相反】的子节点, 两个数该位不同,异或结果才为 1 */ int query(int x) { int now = 0, t, ret = 0; for (int i = 30; i >= 0; i--) { t = (x >> i) & 1; if (nxt[now][t^1] && cnt[nxt[now][t^1]]) { ret |= (1 << i); /* 这一位异或结果为 1 */ now = nxt[now][t^1]; } else now = nxt[now][t]; } return ret; } /* 需要支持删除时:每个节点记录经过次数 cnt, 插入 +1、删除 −1,cnt 为 0 的节点视作不存在 */
图论
阶段三~四 · ★★★★★图论内容很多:最短路、生成树、匹配与网络流、拓扑结构、连通性、强连通分量,甚至还有给条件构造图的构造题。
最短路:堆优化 Dijkstra 模板
C++typedef pair<long long, int> pli; priority_queue<pli, vector<pli>, greater<pli>> pq; /* 小根堆 */ void dijkstra(int s) { fill(d + 1, d + n + 1, INF); d[s] = 0; pq.push({0, s}); while (!pq.empty()) { int u = pq.top().second; pq.pop(); for (auto [v, w] : g[u]) if (d[v] > d[u] + w) { d[v] = d[u] + w; pq.push({d[v], v}); } } } /* 无负权边用 Dijkstra;有负权用 SPFA;点少可用 Floyd */
建图技巧:有时候建出来就会做了
- 虚点建图(Meeting 类问题):集合内两两连边边太多——把每个集合作一个新点,集合点向成员连权值 t 的有向边,成员向集合点连权值 0 的边,之后各跑一遍最短路即可
- 拆点(Kejin Game 类问题):把每个点 i 拆成 i 和 i′ 建源汇,原图问题转化为最小割,直接用最大流求解
- 拓扑排序:Kahn 算法判环(出队数 < n 则有环),也可用于 DAG 上的 DP 与依赖调度
- 构造题(Graph Reconstruction 类问题):由度数序列构图用 Havel–Hakimi 定理——所有点度数从大到小排序,每次考虑一个点向之后的点连边直至满足度数,再重新排序;度数连不完或出现负数则不可构图
数论与数学
阶段三~四 · 推公式能力运用数论知识和计算机快速计算的能力,可以解决许多问题。gcd / lcm、快速幂、筛法、容斥是四大件。
基础模板三连
C++/* 最大公约数(辗转相除) */ long long gcd(long long a, long long b) { return b ? gcd(b, a % b) : a; } /* lcm(a,b) = a / gcd(a,b) * b —— 注意先除后乘防溢出 */ /* 快速幂:O(log b) */ long long qpow(long long a, long long b, long long mod) { long long ret = 1; a %= mod; while (b) { if (b & 1) ret = ret * a % mod; a = a * a % mod; b >>= 1; } return ret; } /* 埃氏筛打素数表 */ for (int i = 2; i <= 100000; i++) isprime[i] = true; for (int i = 2; i <= 100000; i++) if (isprime[i]) for (long long j = 2; i * j <= 100000; j++) isprime[i * j] = false;
容斥原理:正着不好求就求反面
求 1~n 中与 n 互质的数的四次方之和这类问题,正着求不好求,改为对 n 分解质因数后枚举质因数集合使用容斥:
C++/* 前提:fac[] 已存入 n 的所有不同质因子 */ for (long long s = 1; s < (1LL << cnt); s++) { long long now = 1; int bits = 0; for (long long j = 0; j < cnt; j++) if (s >> j & 1) { bits++; now *= fac[j]; } /* now 的倍数(≤n)有 n/now 个,四次方和代入公式 */ /* 四次方和公式:Σk⁴ = n(n+1)(2n+1)(3n²+3n−1)/30 */ /* 奇数个质因子做加,偶数个做减;除以 30 用费马小定理求逆元 */ temp = qpow(now, 4) * (n / now) % mod * (n / now + 1) % mod * (2 * n / now + 1) % mod * ((3 * n / now * n / now + 3 * n / now + mod - 1) % mod) % mod * qpow(30, mod - 2) % mod; /* qpow(30, mod-2) 即 30 的逆元 */ if (bits & 1) ans = (ans + temp) % mod; else ans = (ans + mod - temp) % mod; } /* 最后:用总和减去不互质的部分,得到互质部分的四次方和 */
数学推导示例:A Simple Math Problem(2016 大连站 D)
设 gcd(X, Y) = g,则 X = g·k₁,Y = g·k₂ 且 k₁、k₂ 互质。方程改写为 k₁ + k₂ = a/g,k₁ · k₂ = b/g——说明 g 也是 a、b 的最大公约数。 于是构造一元二次方程 gx² − ax + b = 0,判别式 Δ = a² − 4gb; Δ < 0、Δ 不是完全平方数、或 (a ± √Δ) 不为偶数时输出 No Solution, 否则 X = (a − q)/2,Y = (a + q)/2。
训练方法
锦囊妙计 · 心法源自北航 ACM 集训队学长的实战经验:入门、刷题、团队三个维度的方法论。
入门:刷题 vs 看书
「只看书」容易望而生畏——《算法导论》全书 745 页,想读完整本再去「捕鱼」,无异于读完字典再去看书; 「只刷题」又会留下知识漏洞。正确姿势是螺旋式:看书时配合刷题巩固知识点, 刷题时遇到没学过的算法主动去学,慢慢积累,会有十分可观的收获。
刷题:数量 vs 质量
- 不搞题海战术——做大量题而不考虑质量和效率并不可取
- 遇到不会的题不要丢到一边,积极补题,完全弄明白
- 不做一眼就会的题,浪费时间且无提升
- 写解题报告:题目分析 → 算法分析(复杂度、是否最优)→ 代码实现
- 解题报告也应包括已做出的题,加深理解与记忆
- 参考 Codeforces 大神的 Blog 题解,交流产生灵感
训练:团队 vs 个人
- 团队的好处:互相监督、互相鼓励、分工学习不同专题,减轻学习负担
- 但团队配合建立在个人实力之上——努力提高个人实力才是硬道理
- 三人一台电脑的基本分工:①各自为政式(谁擅长谁上);②指手画脚式(一人敲码、两人观察提议)——最好的策略是两者结合
- 经典协作流程:A 想思路 → 三人讨论细节 → B 操刀写码、C 在旁 debug,A 去看下一题
- 注意避免「两个人同时做了同一道题」的低效情况——开赛前明确分工!
赛场心态(来自学长们的血泪)
装备获取
三大网站 × 经典书籍工欲善其事,必先利其器。训练平台与经典书籍是 ACMer 的两件核心装备——其中下面三个网站,建议立刻收藏,几乎覆盖从入门到区域赛的全部资源。
三大必收藏学习网站(新手首选)
| 网站 | 网址 | 定位 | 新手怎么用 |
|---|---|---|---|
| Codeforces | codeforces.com | 全球最高频的算法竞赛平台(俄罗斯萨拉托夫国立大学团队维护) | 每周多场比赛、Rating 晋级体系;从 Div.2 打起练思维;赛后补题并阅读公开的优胜代码;GYM 组队模拟区域赛 |
| 洛谷 | luogu.com.cn | 中文最大的算法竞赛社区与 OJ 之一 | 题面中文友好;跟官方题单 / 知识点标签循序渐进;递进路线阶段一、二的主战场 |
| OI Wiki | oi-wiki.org | 中文开源算法竞赛知识库(GitHub 协作维护) | 当作「在线字典」:学到哪个专题就查对应章节的系统讲解 + 模板代码 + 推荐习题,查漏补缺 |
其他优质资源(进阶补充)
- AtCoder(atcoder.jp)——日本平台,ABC 每周一场,题目梯度极适合新手
- 牛客(nowcoder.com)——国内比赛 + 企业笔试题库,求职向友好
- Virtual Judge(vjudge.net)——聚合全球各 OJ 题目,方便组队刷题榜
- LeetCode——自带详细官方题解,按 Easy/Medium/Hard 标注,找工作向
三本经典书(怎么读最有效)
《算法竞赛入门经典》(第 2 版)· 刘汝佳
最大的特点是比较基础,理论讲解少、实践演示多。示例代码十分规范简洁,还包含很多开发、测试和调试的技巧(这在算法书中很少见)。 更适合作为练习指导,配合《算法导论》等算法书使用更佳。
《挑战程序设计竞赛》(第 2 版)· 秋叶拓哉 等著
作者是国际知名选手(World Finals 冠军、Google Code Jam 前十),内容结构优秀、循序渐进, 分为准备篇、初级篇、中级篇、高级篇四篇,对每块基础算法逐一攻克,大部分题目附有实例代码与思路说明。 学长评价:把这本书刷完,你已经是金牌水平了。
《算法导论》(Introduction to Algorithms)· CLRS
全面(745 页)、独立性强(各章自成体系,可按需跳读)、浅显易懂不失严谨(含证明与伪代码)。 不要指望一口气读完——把它当工具书,学某个专题时翻对应章节;网上(知乎、CSDN、博客)有大量大佬的学习笔记可以参考。
广义 ACM:值得一并参加的比赛
- CCPC 中国大学生程序设计竞赛——国内另一大赛事体系,赛程与 ICPC 互补
- 蓝桥杯(C/C++、Java 组)——个人赛,入门友好,省赛国赛两级
- 百度之星、编程之美、Topcoder——大厂 / 平台举办,练手 + 简历加分
权衡与建议
值不值得打 ACM?了解竞赛之后,你还需要权衡参与它能带来什么、会让你失去什么,再根据自己的目标做出选择。
利 vs 弊(正反方观点提炼)
| 利 | 弊 |
|---|---|
| 打下坚实的算法基础,大幅提升代码能力、逻辑思维、Debug 心态 | 耗费大量时间,可能挤占做项目、积累工程经验的时间 |
| 获奖充实简历;保研加分;IT 企业笔试机试题型与 ICPC 高度相似 | 面试中可能反被提高难度;奖项本身不保证一切 |
| 结识大量优秀同行,开阔视野,获得内推等圈子资源 | 不搞 ACM 也能通过实习、项目结识良师益友,各有所得 |
五类人群,五条建议
- 学院派:帮助不大——多读论文、去实验室,比竞赛更有用
- 保研党:先保证成绩过硬;获奖是锦上添花
- 考研党:极其耗时,不推荐;机试题型虽相似但难度低很多
- 出国党:在 GPA 等条件保证下可以搞,锦上添花
- 实习党:锻炼思维与代码能力,但会缺项目经历——自己权衡
- 竞赛爱好者:挑战性极强(WF 至今无人全 AC),非常值得拼搏
Q & A 精选
如何平衡学业和 ACM?
课上高效吸收知识,课后专注 ACM(训练、刷题、补题);考期在 ACM 上少投入、专注准备考试。 如果目标极好成绩则必然影响学业,抱着重在参与的心态则很容易平衡——结合自身能力与环境考量。
零基础能打 ACM 吗?
可以。多位受访者都是零基础入坑:北航某学长入学时零基础,研一时拿到 World Final 冠军。 「当你觉得为时已晚,恰恰是最早的时候。」——只要你肯开始,一切都来得及。
什么样的人适合走 ACM 这条路?
当然是喜欢的了:第一,认识到竞赛的好处并想得到它;第二,积极努力;第三,深入思考、权衡利弊。
真题详解 · 2016–2026
点击选项即判分收录 2016–2019 年亚洲区域赛真题(公开题解整理)、2020–2024 年考生回忆版改编题、 2025 年官方样题风格题,以及依据近年赛制编写的 2026 年模拟预测题。 回忆版与改编题可能与原题表述略有出入。选择题点击选项立即判分;思路题点击「查看解析」展开——建议先自己想 10 分钟再看答案。
e[x][clique[j]] 是否全为 true,全部相连才加入(保证团性质);③ sz 达到 S 时 ans++ 并回溯;④ 回溯时执行 sz-- 把点移出,重新标记为未使用,避免遗漏其他方案;⑤ 多组数据结束时清空邻接表、邻接矩阵与 ans。
易错点:注意输出的是「完整的鱼」和「吃了一部分鱼」两种;多组数据要清空优先队列。
(i − prev[p]) × Σ_{j=i}^{n} 1/j;③ Σ1/j 用分数形式的后缀和维护(分子分母分别累加,注意约分);④ 更新 prev[p] = i。整体复杂度约 O(n log A),避免了 O(n²) 枚举所有窗口。
判环原理:环上的每个点都有环内前驱,入度永远无法减到 0,因此永远不会入队——出队数 < n 即存在环。
扩展:把普通队列换成优先队列(小根堆),可以得到字典序最小的拓扑序;拓扑序也是 DAG 上 DP 的合法计算顺序。
C++for (int s = mask; s; s = (s - 1) & mask) { /* s 依次取 mask 的每个非空子集,从大到小 */ } /* 若还需包含空集,循环结束后单独处理 s = 0 */解析:每次 s−1 把最低位的 1 借走,再与 mask 按位与,恰好跳到下一个更小的子集且不重不漏。总时间枚举所有掩码的所有子集为 O(3ⁿ)。这个技巧在子集卷积、枚举断点转移的 DP 中大量出现。
x += lowbit(x),query 用 x -= lowbit(x)。注意下标必须从 1 开始,出现 0 会让 lowbit 死循环。ans += query(aᵢ − 1)(这些数在 aᵢ 右侧且比它小,构成逆序对),再把 aᵢ 插入 update(aᵢ, 1);③ 累加即为逆序对总数,复杂度 O(n log n)。对照:归并排序求逆序对复杂度相同(合并时统计跨越左右两半的逆序),两种写法都要会。