GESP8 考前晨读卡 · 完整版
2026-06-27 下午考 · 弱点 = 倍增 LCA + MST 路径最值(真题 P14080)· 上午只确认手感,不开新题
用法:每个模板先盖住代码默写 → 卡住再看答案 → 红框两张是你的失分点,优先默。看完直接进考场,别再翻旧账、别熬新难题。
三句心法:① 读数据范围反推复杂度再动手 ② 40 分钟没正解立刻转部分分 ③ 交卷前走一遍最后一节的检查清单。
一、弱点优先(倍增 + MST,必默)
★ 倍增 LCA / k级祖先 / 树上距离 3.1
const int LOG = 20;
int dep[N], up[N][LOG];
vector<int> g[N];
void bfs(int rt) { // 大数据用 BFS 防爆栈
queue<int> q; dep[rt] = 1; up[rt][0] = 0; q.push(rt);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : g[u]) if (!dep[v]) {
dep[v] = dep[u] + 1; up[v][0] = u;
for (int j = 1; j < LOG; j++) up[v][j] = up[up[v][j-1]][j-1];
q.push(v);
}
}
}
int kth(int u, int k) { // u 的第 k 级祖先
for (int j = 0; j < LOG; j++) if (k >> j & 1) u = up[u][j];
return u;
}
int lca(int a, int b) {
if (dep[a] < dep[b]) swap(a, b);
for (int j = LOG-1; j >= 0; j--)
if (dep[up[a][j]] >= dep[b]) a = up[a][j]; // 对齐深度
if (a == b) return a;
for (int j = LOG-1; j >= 0; j--)
if (up[a][j] != up[b][j]) { a = up[a][j]; b = up[b][j]; } // 同步上跳
return up[a][0];
}
易错 · 树上距离 = dep[a]+dep[b]-2*dep[lca](背下来) · 同步上跳是 不相等才跳,最后返 up[a][0] · 题目不保证 a≠b,对齐后 a==b 要直接返回。 自测 P3379 → P10289 / P13019
★ 倍增维护路径最值(MST 路径最大边) 3.4
int up[N][LOG], mx[N][LOG]; // mx[v][0] = v 到父亲的边权
// 建表(在 bfs 里和 up 一起):
// up[v][j] = up[up[v][j-1]][j-1];
// mx[v][j] = max(mx[v][j-1], mx[up[v][j-1]][j-1]);
int pathmax(int a, int b) { // a→b 路径上最大边权
int res = 0;
if (dep[a] < dep[b]) swap(a, b);
for (int j = LOG-1; j >= 0; j--)
if (dep[up[a][j]] >= dep[b]) { res = max(res, mx[a][j]); a = up[a][j]; }
if (a == b) return res;
for (int j = LOG-1; j >= 0; j--)
if (up[a][j] != up[b][j]) {
res = max(res, max(mx[a][j], mx[b][j]));
a = up[a][j]; b = up[b][j];
}
return max(res, max(mx[a][0], mx[b][0])); // 别漏到 LCA 的最后两条边
}
套路 · 先 Kruskal 建(最大)生成树 → 树边挂 mx[v][0] → 两点路径最值用 pathmax。不会满分就先暴力跳父亲拿部分分。 自测 P14080(删边重连) → P1967 货车运输
★ Kruskal + 并查集(MST) 2.1
int fa[N];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); } // 路径压缩
// 读边到 vector<E>{u, v, w};
for (int i = 1; i <= n; i++) fa[i] = i; // 别忘初始化
sort(e.begin(), e.end()); // 按 w 升序
long long tot = 0; int cnt = 0;
for (auto &x : e) {
int ru = find(x.u), rv = find(x.v);
if (ru != rv) { fa[ru] = rv; tot += x.w; cnt++; } // 不同集合才连
}
// cnt == n-1 → 连通,tot 即答案;否则图不连通(常需输出 -1)
易错 · 总权用 long long · find 不同才合并(防环) · 无向图判环:加边前已同根就成环 · 有向图判环用拓扑,不用并查集。 自测 P3366 → P14080
并查集(独立应用 · 按大小合并) 3
int fa[N], sz[N];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
// init: for (i) { fa[i] = i; sz[i] = 1; }
void merge(int a, int b) {
a = find(a); b = find(b);
if (a == b) return;
if (sz[a] < sz[b]) swap(a, b); // 小的挂到大的下
fa[b] = a; sz[a] += sz[b];
}
// 连通块数 = #{ fa[i] == i };每成功合并一次,块数 -1
易错 · 判连通用 find(a)==find(b) · 数连通块在合并后统计根 · 有向图判环改拓扑(见下)。
二、图论:最短路 / 生成树 / 拓扑
Dijkstra 堆优化(正权单源) 1.2
typedef pair<long long, int> pli; // (距离, 点)
vector<pli> g[N]; long long dist[N]; // g[u] 存 {w, v}
// init dist[i] = INF;
priority_queue<pli, vector<pli>, greater<pli> > pq; // 小根堆,两 > 留空格
dist[s] = 0; pq.push(make_pair(0LL, s));
while (!pq.empty()) {
pli t = pq.top(); pq.pop();
long long dd = t.first; int u = t.second;
if (dd > dist[u]) continue; // 过期项必须跳
for (size_t i = 0; i < g[u].size(); i++) {
long long w = g[u][i].first; int v = g[u][i].second;
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w; pq.push(make_pair(dist[v], v));
}
}
}
易错 · dist 用 long long · 小根堆三件套别漏 · 无向图加两遍边 · 八级只用 Dijkstra,别用 auto [d,u](旧评测报错)。 自测 P4779 / P10726
Floyd 全源最短路 1.1 · n≤300~500
const long long INF = 1e18;
long long d[305][305];
// init d[i][j] = INF, d[i][i] = 0, 读边取 min(无向加反向)
for (int k = 1; k <= n; k++) // k 必须最外层!
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (d[i][k] < INF && d[k][j] < INF)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
// 传递闭包: f[i][j] |= f[i][k] && f[k][j];
易错 · k 在最外层(写到里面就错) · 松弛前判 INF 防溢出 · 重边初始化取 min · 可处理负权边,不能有负权环。 自测 B3647 → P15805 增量 Floyd
Prim 朴素 O(n²)(稠密图 / 矩阵) 2.2
int g[N][N]; int dis[N]; bool vis[N]; // dis[v]: v 到"当前树"的最短连边
// init dis[i] = INF; dis[1] = 0;
long long tot = 0;
for (int i = 1; i <= n; i++) {
int u = -1;
for (int v = 1; v <= n; v++) // 选树外离树最近的点
if (!vis[v] && (u == -1 || dis[v] < dis[u])) u = v;
if (dis[u] == INF) break; // 图不连通
vis[u] = true; tot += dis[u];
for (int v = 1; v <= n; v++) // 用新点 u 更新其它树外点
if (!vis[v] && g[u][v] < dis[v]) dis[v] = g[u][v];
}
易错 · dis[v] 是"到树的最短边",不是源点最短路(别和 Dijkstra 混) · 稀疏图给边表用 Kruskal,稠密图/矩阵用 Prim。 自测 P1546 最短网络
拓扑排序 + DAG 上 DP 4
vector<int> g[N]; int indeg[N]; // 有向边 u→v: g[u].push_back(v), indeg[v]++
queue<int> q; vector<int> order;
for (int i = 1; i <= n; i++) if (!indeg[i]) q.push(i);
while (!q.empty()) {
int u = q.front(); q.pop(); order.push_back(u);
for (int v : g[u]) if (--indeg[v] == 0) q.push(v);
}
// 判环: order.size() < n ⇒ 有环
// DAG-DP: 按 order 转移 dp[v] = best(dp[v], dp[u] + w);
易错 · 判环看入队总数是否 < n · DAG-DP 必须按拓扑序转移(u 先于 v 算完) · 只在 --indeg[v]==0 时入队。 自测 P11380 判环 → P10726 隐式图 DAG-DP
三、树形 DP / 树上技巧 / 区间
树形 DP · 直径 / 最长链 7
int ans = 0; bool vis[N];
int dfs(int u, int p) { // 返回以 u 为顶向下的最长链(节点数)
vis[u] = true;
int d1 = 0, d2 = 0; // 最长、次长向下链
for (int v : g[u]) if (v != p) {
int d = dfs(v, u);
if (d > d1) { d2 = d1; d1 = d; } else if (d > d2) d2 = d;
}
ans = max(ans, d1 + d2 + 1); // 过 u 的最长路径(节点数)
return d1 + 1; // 只把最长链给父亲
}
// 森林: for (i) if (!vis[i]) dfs(i, 0);
易错 · 过 u = d1+d2+1,返回父亲只 d1+1 · 森林靠 vis 各跑一次 · 有约束(如颜色不同)只在满足时更新 d1/d2 · 看清要节点数还是边数。 自测 P11251 → P10725
树上差分(点 / 边) 8 · 配 LCA
// 路径 u→v 每点 +d, L = lca(u, v)
diff[u] += d; diff[v] += d; diff[L] -= d; diff[fa[L]] -= d; // 点差分
// 边差分(挂深端点): diff[u] += d; diff[v] += d; diff[L] -= 2*d;
int dfs(int u, int p) { // 自底向上汇总
int s = diff[u];
for (int v : g[u]) if (v != p) s += dfs(v, u);
val[u] = s; return s;
}
易错 · 点差分扣 fa[L],边差分扣 2*L(两者端点扣法不同) · 先建好 LCA 的 up/dep/fa · 边的值记在深的端点,根不对应任何边。 自测 P3128 / P11967
ST 表(静态区间 max/min/gcd) 3.5
int st[N][20], lg[N];
// 预处理 st[i][0] = a[i]; lg[i] = lg[i/2] + 1 (i >= 2)
for (int j = 1; j < 20; j++)
for (int i = 1; i + (1 << j) - 1 <= n; i++)
st[i][j] = max(st[i][j-1], st[i + (1 << (j-1))][j-1]);
// 查询 [l, r]
int k = lg[r - l + 1];
int ans = max(st[l][k], st[r - (1 << k) + 1][k]); // 两块重叠覆盖
易错 · 右块起点 r-(1<<k)+1(贴右端,别写 l+...) · 只对 max/min/gcd 成立,求和用前缀和 · 询问极多配快读。 自测 P3865
序列 / 状态倍增跳跃 3.3
const int LOG = 18;
int nxt[N], f[N][LOG]; // 序列要开 2n
// f[i][0] = nxt[i]; 走不动的位置 → 哨兵(指向不会再跳进的下标)
for (int j = 1; j < LOG; j++)
for (int i = 1; i <= n; i++)
f[i][j] = f[f[i][j-1]][j-1];
// 落点 ≤ R 前提下尽量多跳并计步
int cur = u, steps = 0;
for (int j = LOG-1; j >= 0; j--)
if (f[cur][j] <= R) { cur = f[cur][j]; steps += 1 << j; }
易错 · 哨兵必须设全(末尾 n+1/n+2 都指哨兵),否则跳进 0 号把 2^k 全加上 · nxt 常用双指针/倒序记录预处理 · 环形先破环成链开 2n。 自测 P10264 接竹竿 → P14924 宝石项链
四、组合 / 数论 / 计数
杨辉三角递推求组合数(最稳) 5
long long C[N][N]; // C[i][j] = C(i, j),n ≤ 2000~5000
for (int i = 0; i < N; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++)
C[i][j] = (C[i-1][j-1] + C[i-1][j]) % MOD;
}
易错 · 边界 C(i,0)=1,内层只算 j≤i · 纯加法,模数不是质数也能用 · n 很大(>5000)打不下表才改阶乘+逆元。 自测 P2822 / P3414
快速幂(指数倍增) 3.2
typedef long long ll;
ll power(ll a, ll b, ll mod) { // a^b % mod
ll r = 1; a %= mod;
while (b > 0) { if (b & 1) r = r * a % mod; a = a * a % mod; b >>= 1; }
return r;
}
易错 · 结果 r 初值 1,全程取模 + long long · 求逆元(MOD 质数):inv(x)=power(x,MOD-2)(费马小定理)。
阶乘 + 乘法逆元(大组合数取模) 6
const int MOD = 1e9 + 7;
ll fac[N];
// fac[0] = 1; for (i) fac[i] = fac[i-1] * i % MOD;
ll inv(ll x) { return power(x, MOD - 2); } // 费马,MOD 须质数
ll C(int n, int m) {
return fac[n] * inv(fac[m]) % MOD * inv(fac[n-m]) % MOD;
}
易错 · "÷x" = "×inv(x)",前提 MOD 是质数 · 若 MOD=1e9(非质数)费马失效 → 改杨辉递推或直接整除 · 全程 long long 随时取模 · 批量逆元 inv[i]=(p-(p/i)*inv[p%i]%p)%p。 自测 P10112 → P13020
约数 / 倍数计数(调和级数筛) 附
// 每个数把贡献加给它的倍数,O(K log K)
for (int d = 1; d <= K; d++)
for (int m = d; m <= K; m += d)
cnt[m] += ...; // 如 +1 统计因数个数 / +d 求约数和
触发 · 看到"对每个数枚举其倍数/因数贡献"就用这个,别写 O(K√K) 逐个分解。 自测 P10263 / P2926
五、交卷前最后 15 分钟 · 逐项打勾(最高频失分点)
- long long:边权 / 费用 / 求和(到 1e9 求和必溢出 int)
- 图不连通 → 输出
-1(MST / 瓶颈题必查)
- 取模题每步取模(别只在最后取一次)
- 数组开够大;序列倍增 / 破环成链要开 2n
- 结点 0 编号 vs 1 编号 是否对齐
- 无向边加两遍;重边 / 自环是否处理
- dist[] / dep[] / 邻接表的初始化(INF / 0)
- 差分:修正量减在最终结果,别减进差分数组里被传播
- 倍增哨兵设全(走不动的位置不要跳进 0 号)
- 文件 / 标准 IO 看清;读入函数用对(cin / scanf)
六、考场策略 5 条
- 先扫客观题,明显会的先拿分,不在单题上纠缠;判断题重点查 C++ / 复杂度 / 图论树结论。
- 编程题先读数据范围 → 反推复杂度 → 再决定模板:n≤500 想 Floyd / n²;n≈1e5 想 n log n。
- 每题先写好输入输出 + 样例 + 核心函数骨架,保证有可运行程序,再优化。
- 40 分钟没正解,立刻转部分分:暴力 / 小数据 / 单考点版本(GESP 按子任务给分,压轴拿 50%+ 就赚)。
- 最后留 15 分钟,只对照上面的检查清单逐项过,不再动算法。