1. 先说清楚:蓝桥杯考的是“在规定时间里写出完整可运行的程序”
年年备赛季都有同学问我同一个问题:算法模板到底有没有用?我的答案非常明确——有用,而且是非常有用。但前提是你得知道蓝桥杯这比赛到底在考什么,不然模板就只是收藏夹里吃灰的代码。
很多人对蓝桥杯的理解还停留在“算法竞赛=智力比拼”。实际打下来你会发现,蓝桥杯的难度并不在于让你发明一个新算法,而在于:给你一个时间有限的环境,要求你把一道带有明确套路背景的题目完整做出来,交上去能编译、能跑、结果正确。它不像某些比赛那样可以反复提交、靠反馈修正,蓝桥杯的编程大题基本是提交即定局,程序跑挂了就是挂了。在这种规则下,决定你分数上限的往往不是“会不会做”,而是“能不能在有限时间内稳定地把会做的题写出来”。
1.1 蓝桥杯和ACM式比赛的最大差异
如果你接触过ACM/ICPC这类比赛,你会发现它们更强调“临场反应”。一道题出来了,队友互相讨论、猜复杂度、笔算推导,然后上线提交,错了还能罚时重交。蓝桥杯不一样:
- 单个选手独立作战,没有人帮你看代码、找问题。
- 提交次数不是无限刷的,每次提交都可能直接影响最终成绩。
- 比赛时间虽长,但题量不小,填空题、编程题混合,你必须自己控制节奏。
- 代码写完基本没有机会大改,现场编译一次、跑几个样例,就要交了。
所以蓝桥杯的备考逻辑和ACM完全不同。ACM选手可以靠临场推公式、现写数据结构,因为他们的反应速度和编码能力已经通过海量训练练出来了。但大部分蓝桥杯选手,尤其是刚接触算法竞赛的同学,做不到“现场花20分钟从零写一个树状数组还不出错”。因此,把常用算法以模板形式固定下来,赛前达到肌肉记忆级别,是性价比最高的备考方式。
我见过太多人,平时看题解觉得“也就那样”,真上了赛场,光是把输入输出、初始化、边界判断这些基建部分理顺,就要花掉大量时间。等主算法写完,已经没时间查坑了。这就像考试前你才临时背公式,而不提前把它印在脑子里。
1.2 模板解决的三件事:手速、精度、心态
我愿意把模板在蓝桥杯中的作用总结为三层:
- 手速层。凡是写过多遍的模板,你上考场就是默写。别人还要想“树状数组add怎么写的”,你已经写完了,还能顺手检查一遍。按每道题省下5到10分钟计算,一份完整模板库在整场比赛中能救回将近一小时,这是决定能不能做出最后一题的关键。
- 精度层。模板是反复验证过的,边界条件、数组下标、类型溢出都踩过坑。你不再需要边写边想“这里要不要long long”“数组从0还是从1开始”,照搬就完事。可靠性高了,交卷时心不虚。
- 心态层。比赛开始先默写模板,是一种热身。把熟悉的代码“哐哐哐”打出来,人会迅速进入状态,不慌张。相反,一开始就面对难题死磕,越写越没底,后面全是连锁崩溃。
所以本文接下来要做的,就是把一份我认为最适合蓝桥杯场景的算法模板体系拆给你看。我不会把网上几百页的算法大全复制过来,而是按照历年考点出现频率、蓝桥杯题型特点、个人实战中踩过的坑,整理出一份可以直接上手的清单和源码。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 模板优先级排序:别把时间浪费在低频算法上
很多人的误区是,觉得模板越多越好,最后背了几十份,考场上一个都想不起来。真正合理的做法是分梯队准备,优先保证高频考点不出错,有余力再往低频但高分的模块扩张。
我先说结论:蓝桥杯省赛和中高级组别里,最高频的考点是模拟、枚举、搜索、排序、前缀和、二分、简单数论、并查集、动态规划。字符串、图论里的最短路和最小生成树出现频率稍低,但也不能完全不备。至于那些特别冷门的算法,比如后缀自动机、网络流、平衡树,省赛阶段基本不用考虑;国赛如果想冲击高分,另说。
2.1 第一梯队:每场必出的基础板子
这一个梯队,要求你闭着眼睛都能写出来,错一个字符都算不合格。它们包括:
- 快读/快写模板。蓝桥杯有些题目的输入规模是十万、百万级的,用默认cin在一些评测环境下会吃大亏。虽然很多情况下开了同步关闭就够,但一手稳定的快读能让你在面对大数据时完全不慌。
- 前缀和与差分。区间求和、子矩阵求和、区间修改,蓝桥杯模拟题和思维题里大量出现。前缀和模板不到20行,但能解决一大类问题。
- 二分答案。二分查找的三类边界写法必须烂熟于心。蓝桥杯很喜欢出“最小值最大化”“最大值最小化”的题,本质就是二分加判定函数。
- 快速幂与GCD。数论题的命根子。快速幂处理大指数取模,扩展GCD偶尔也会出现在同余类题目里。
- DFS与BFS。搜索题是蓝桥杯的重头戏,迷宫类、连通块类、暴力枚举类,全都建立在DFS/BFS骨架上。注意,这里不只是会写,还要会加剪枝,会记录访问状态,会回溯。
2.2 第二梯队:高频考点但需要理解边界
第二梯队属于“必须见过、必须练熟,但可以允许多花一点时间理解”的部分。
- 并查集。蓝桥杯很喜欢把并查集放在看似是图论、实际上是连通性判断的题目里。模板很短,但路径压缩、父节点初始化的细节不能错。
- 树状数组。它和线段树相比,代码量小、常数低,适合单点修改加区间查询的问题。蓝桥杯很多题只需要树状数组就够了,不必上一整棵线段树。
- 最短路。Dijkstra的堆优化写法一定要会。蓝桥杯的图论题数据范围通常不会太大,很少要求SPFA那种极限优化,但优先队列跑一遍Dijkstra基本是标配。
- 动态规划。LIS、背包、区间DP是重点。模板本身没有统一的“板子”,但状态转移方程的套路可以总结:01背包、完全背包、多重背包的分类写法,以及二维DP的初始化细节,格外值得记录。
- 素数筛与质因数分解。用欧拉筛求出一百万以内的素数表,是解决因子类题目的基础。配合唯一分解定理,能做不少纯粹的数论小题。
2.3 第三梯队:低频高分,看精力选择
第三梯队包括KMP、字典树、拓扑排序、最小生成树、数位DP、状态压缩DP等。这些算法不是不出,而是出场频率分散、单年考察概率有限。如果备考时间充裕,可以准备;如果只剩两周,把第一第二梯队压到100%熟练比背第三个梯队更划算。
我自己的经验是:蓝桥杯省赛想要拿省一,第三梯队准备一半就行;冲国赛,第三梯队里的字典树、拓扑排序、最小生成树才值得拿出来练。
2.4 模板覆盖度速查表
下面是我实际备赛时用的覆盖度清单,优先级越靠前,投入时间越要倾斜。你可以拿它自测,看见一个项目立刻写出代码,就算过关。
| 模板模块 | 优先级 | 常见出题场景 | 我的提醒 |
|---|---|---|---|
| 快读快写 | 必背 | 大输入、多次输出 | 整数不定长用快读,浮点数慎用 |
| 前缀和 / 差分 | 必背 | 区间和、子矩阵、区间加减 | 前缀和数组记得开long long |
| 二分答案 | 必背 | 最小化最大值、可行性判断 | 边界建议统一用左闭右开 |
| DFS / BFS | 必背 | 迷宫、连通块、枚举状态 | 回溯和去重是重点 |
| 快速幂 / GCD | 必背 | 大数取模、同余、最大公约数 | 模数可能很大,乘法要防溢出 |
| 并查集 | 高频 | 连通关系、集合合并 | 合并前先find,否则会出链 |
| 树状数组 | 高频 | 单点改、区间查 | 下标从1开始,这是经典坑 |
| 欧拉筛 | 高频 | 素数判断、质因数分解 | 数组大小比上限多开几个 |
| Dijkstra | 高频 | 带权最短路、路径成本 | 边权可能为long long |
| 背包DP | 高频 | 资源分配、选取问题 | 一维数组倒序更新是死规矩 |
| 最小生成树 | 中频 | 连全部节点最小成本 | Kruskal排序后并查集合并 |
| 拓扑排序 | 中频 | 依赖关系、先后顺序 | 入度为0的节点入队 |
| KMP | 中频 | 子串匹配、重复串 | next数组含义必须理解 |
| 数位DP | 低频 | 数字满足某性质的数量 | 记忆化参数要完整 |
这张表不是让你背下来,而是让你对着它去自测。能够流畅默写出每一项,再谈刷题。
3. 手写一套可以直接开箱的模板库(附避坑注释)
很多人找模板喜欢去网上复制一大段,但模板这个东西,如果自己一行没敲过,上了考场等于没有。所以我建议把下面的模板当成“母版”,自己重新打一遍,把变量名、注释改成自己的习惯。这里我以C++为例,因为蓝桥杯C++组的覆盖面最大;Python选手可以对应地改造成Python版本,核心思路完全一样。
3.1 基础IO与主程序骨架
一个稳定的主程序骨架,能帮你省下大量调试时间。我的习惯是全局变量统一放在上方,所有数组开在main外面,这样不占栈空间,也方便调试时观察数据。
cpp复制#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// 数组常量大一点,宁多勿少
const int MAXN = 1000005;
ll a[MAXN], diff[MAXN], prefix[MAXN];
// 快速读取:处理大量整数输入时比cin稳定
ll readll() {
ll x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 蓝桥杯本地调试时可以配合freopen读文件
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
int n;
cin >> n;
// 主体逻辑写在这里
return 0;
}
避坑提示:
#include <bits/stdc++.h>在蓝桥杯官方编译环境下一般没问题,但如果你用老旧编译器,最好替换成显式头文件。平时练习可以同时准备两种版本。- 数组开在全局区,默认初始化为0,这能省掉memset。
- 如果数据量在1e5以下,
ios::sync_with_stdio(false)加cin.tie(nullptr)完全够用;只有数据量到1e6以上,才值得用上面那套自定义快读。
3.2 数论三件套:GCD、快速幂、欧拉筛
数论问题最大的隐患是溢出。快速幂的中间乘法很容易超过int范围,所以我统一用long long。
cpp复制ll gcd(ll a, ll b) {
return b == 0 ? a : gcd(b, a % b);
}
ll lcm(ll a, ll b) {
// 先除后乘,防止中间结果溢出
return a / gcd(a, b) * b;
}
ll qpow(ll a, ll b, ll mod) {
ll res = 1 % mod;
while (b) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
vector<int> primes;
bool notPrime[MAXN];
void eulerSieve(int n) {
for (int i = 2; i <= n; i++) {
if (!notPrime[i]) primes.push_back(i);
for (int p : primes) {
if (i * p > n) break;
notPrime[i * p] = true;
if (i % p == 0) break;
}
}
}
这段代码的价值在于:欧拉筛的break条件很关键,i % p == 0立刻停止,能保证每个合数只被最小质因子筛掉,复杂度线性。如果你只记埃氏筛的写法也可以,但面对1e6级别的数据,欧拉筛更稳。
3.3 树状数组和并查集:最容易被现场写错的两个结构
树状数组看起来代码短,写着写着就容易出问题。最常见的是下标从0开始用,导致lowerbit计算失效。记住:树状数组下标一律从1开始,对外的数据转换自己处理。
cpp复制int tree[MAXN];
int n;
void add(int idx, int val) {
while (idx <= n) {
tree[idx] += val;
idx += idx & (-idx);
}
}
ll sum(int idx) {
ll res = 0;
while (idx > 0) {
res += tree[idx];
idx -= idx & (-idx);
}
return res;
}
// 区间查询 [l, r]
ll rangeSum(int l, int r) {
return sum(r) - sum(l - 1);
}
如果题目要“区间加、单点查询”,你不需要改造树状数组,把它当成差分数组的载体就行:在 add(l, val)、add(r+1, -val) 后,sum(i) 就是原数组第i位的值。这个技巧刷题时经常用。
并查集的核心是路径压缩,代码就这么几行,但少了合并前的find就会出隐藏bug。
cpp复制int fa[MAXN];
void init(int n) {
for (int i = 1; i <= n; i++) fa[i] = i;
}
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void unite(int a, int b) {
int ra = find(a);
int rb = find(b);
if (ra != rb) fa[ra] = rb;
}
bool isSame(int a, int b) {
return find(a) == find(b);
}
我见过有人在merge时写成 fa[a] = b,没调用find,结果后续查询时链路越来越长,既慢又错。这类细节点到即止,但必须形成条件反射。
3.4 搜索模板:DFS、BFS和Dijkstra
搜索是蓝桥杯的必考基本功,但很多人写DFS时不注意回溯,导致状态污染。我的习惯是:能不用全局变量标记就不加,标记了之后一定要在递归返回时撤销。
cpp复制vector<int> g[MAXN];
bool vis[MAXN];
void dfs(int u) {
vis[u] = true;
// 处理节点u
for (int v : g[u]) {
if (!vis[v]) dfs(v);
}
}
void bfs(int s) {
queue<int> q;
vis[s] = true;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : g[u]) {
if (!vis[v]) {
vis[v] = true;
q.push(v);
}
}
}
}
回溯式的DFS与上面这个“访问后标记永久化”的场景不同。如果题目要求枚举所有路径或排列组合,标记必须在退出时清空:
cpp复制void dfs2(int u, int depth) {
if (depth == limit) {
// 得到一个完整组合
return;
}
for (int i = 1; i <= n; i++) {
if (!used[i]) {
used[i] = true;
dfs2(i, depth + 1);
used[i] = false; // 回溯清理
}
}
}
最短路模板我选用堆优化的Dijkstra,因为它不存在负边问题,也比SPFA稳定。
cpp复制struct Node {
int v;
long long w;
bool operator > (const Node& other) const {
return w > other.w;
}
};
vector<pair<int, long long>> g2[MAXN];
long long dist[MAXN];
void dijkstra(int s) {
memset(dist, 0x3f, sizeof(dist));
priority_queue<Node, vector<Node>, greater<Node>> pq;
dist[s] = 0;
pq.push({s, 0});
while (!pq.empty()) {
auto [u, d] = pq.top();
pq.pop();
if (d != dist[u]) continue;
for (auto [v, w] : g2[u]) {
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
pq.push({v, dist[v]});
}
}
}
}
这里用到了C++17的结构化绑定,蓝桥杯官方编译器基本都支持。如果你用老版本标准库,就改成传统的方式访问first、second。if (d != dist[u]) continue;这行是防止重复更新旧状态的通用写法,没有它也没错,但会导致堆中有大量无用节点,数据大了会超时。
4. 真题场景实录:模板是怎么被“拆开”用的
模板不是摆在那里看的,它必须能快速套进具体的题目里。这一节我用三组蓝桥杯里高频出现的场景,来说明“识别模型、调用模板”的完整思路。
4.1 日期与枚举题:暴力搜索模板的灵活切换
蓝桥杯特别喜欢出日期类题目。它的本质是“给你一个时间范围,让你枚举每一天,判断某件事情是否成立”。比如回文日期、闰年计数、两个日期之间有多少天,等等。这类题不需要高深算法,但日期处理的边界极其琐碎:闰年规则、月底最后一天、跨年、跨世纪。如果你每次现场推理,很容易漏掉闰年条件。
我的做法是提前写好一个“日期工具模板”,包括闰年判断、一个月有多少天、从基准日期到目标日期经过了多少天。
cpp复制bool isLeap(int y) {
return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0);
}
int monthDays[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int getDaysOfMonth(int y, int m) {
if (m == 2 && isLeap(y)) return 29;
return monthDays[m];
}
long long daysFromEpoch(int y, int m, int d) {
long long days = 0;
for (int i = 1; i < y; i++) {
days += isLeap(i) ? 366 : 365;
}
for (int i = 1; i < m; i++) {
days += getDaysOfMonth(y, i);
}
days += d;
return days;
}
这段代码不是最高效的,但对于蓝桥杯的日期范围够用了。用的时候,只要求两个日期都转成“从公元1年1月1日开始的偏移量”,然后相减取绝对值,简单直观,不会出现跨年借位问题。
真正的考试场景往往比这个复杂一点点:题目可能让你把一个8位数字组成YYYYMMDD的日期拆开,判断是否满足回文条件。这时候你可以用下面这段枚举:
cpp复制for (int y = 1000; y <= 9999; y++) {
for (int m = 1; m <= 12; m++) {
for (int d = 1; d <= getDaysOfMonth(y, m); d++) {
int dateNum = y * 10000 + m * 100 + d;
// 检查dateNum是否满足题目附加条件
}
}
}
把日期枚举循环写熟,蓝桥杯里一堆“回文日期”“重组日期”“第几个纪念日”的题目都能快速拿下。这类题看着繁琐,实际就是套壳的暴力枚举,模板的价值体现得最明显。
4.2 区间和与区间修改:前缀和、差分和树状数组的分工
蓝桥杯里有一类出现频率极高的题:给你一个数组,做若干次区间操作,最后问你某些位置的值或者某段区间的和。遇到这种题,先不要急着写线段树。蓝桥杯的大部分数据范围,用前缀和加差分就足够解决。
“多次操作后求单点值”用差分数组。假设你要对区间[l, r]加上val,只需要改两个位置:
cpp复制diff[l] += val;
diff[r + 1] -= val;
所有操作结束之后,对差分数组做一遍前缀和,就能还原出每个位置的最终值。这个模板我强烈建议按“肌肉记忆”掌握,因为它解题速度极快。
“多次询问区间和”用前缀和数组。先预处理:
cpp复制prefix[i] = prefix[i - 1] + a[i];
然后每个询问 [l, r] 的答案就是 prefix[r] - prefix[l - 1],复杂度O(1)。
但如果操作和查询是交错的,也就是先改一个点、又马上查询区间,那就不能用简单的前缀和了,需要引入树状数组。比如“支持单点增加、区间求和”的题目,直接调用我在3.3里写的树状数组模板。这里有一个典型的模型转换:蓝桥杯有些题会把数组下标从0给出来,而树状数组需要下标从1开始,你只需要在读入时把下标全部加1即可。这个细节很蠢,但每年都有人栽在这里。
我的建议是:拿到区间类题目,先问自己三个问题:
- 是一次性操作后统一查询,还是操作查询交错?
- 是单点修改,还是区间修改?
- 是单点查询,还是区间查询?
根据答案去选择差分、前缀和、树状数组。这个过程熟练之后,基本看到题就能锁定模板。
4.3 图连通性问题:并查集的“并”和“查”时机
蓝桥杯里有一类题,看着像图论,实际核心是“维护点与点之间的连通关系”。比如朋友圈合并、等价类划分、网络连通性判断、最小生成树的前置操作。
这类问题最标准的解法就是并查集。它的使用时机很有讲究:如果题目先把所有边给你,然后问你哪些点连通,那你可以一次性union完;如果题目要求“边加入边查询”,那就得随时调find判断。
给你一个几乎每年出现的模型:有N个节点,M条边,每条边连接两个点。问你最终有多少个连通块。模板用起来非常顺手:
cpp复制init(n);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
unite(u, v);
}
int cnt = 0;
for (int i = 1; i <= n; i++) {
if (find(i) == i) cnt++;
}
cout << cnt << endl;
注意,连通块数量是统计“根节点是自己的节点数”。这个套路我在好几场模拟赛中遇到过,第一次没经验写成了 fa[i] == i,后来改成 find(i) == i,因为路径压缩后父节点不一定直接等于根。
有些进阶一点的问题会要求:每合并一次,就把当前最大集合的大小输出。此时你可以在并查集旁边维护一个size数组,合并时更新:
cpp复制int sz[MAXN];
void initSize(int n) {
for (int i = 1; i <= n; i++) {
fa[i] = i;
sz[i] = 1;
}
}
void uniteSize(int a, int b) {
int ra = find(a);
int rb = find(b);
if (ra != rb) {
fa[ra] = rb;
sz[rb] += sz[ra];
}
}
这片段虽然只是多维护了一个数组,但能让题目从“单纯的连通性判断”升级成“动态维护集合大小”,是蓝桥杯真题中很常见的考法。
5. 让模板真正长在脑子里:内化练习与考场禁忌
模板写出来了,只是万里长征第一步。真正的问题在于:你怎么保证赛场上能一字不差地写出来?答案只有一个——反复手动默写,直到形成肌肉记忆。
5.1 “三天抄一遍,两周默一遍”的内化节奏
我记得自己备赛期间的模板复习节奏是:第一遍从网上找一份高质量模板,照着抄进自己的笔记,同时理解每一行的作用。抄完不算数,第二天盖住源码,自己重新写一遍。然后每三天把整套模板重抄一次。
到了赛前两周,开始“默写模式”:每天早上花20分钟,把树状数组、快速幂、并查集、Dijkstra、欧拉筛这五个最核心的模板依次默写出来。最初会卡壳,比如欧拉筛的break条件想不起来、Dijkstra的堆定义敲错,错一次就标注一次。反复纠正之后,你的手会对代码形成记忆,根本不需要大脑逐行思考。
这套方式的妙处在于,它强迫你关注细节,而不是“眼熟”。看题解觉得自己全会了,和合上屏幕白板手写一遍,完全不是一回事。
5.2 模板库应该长什么样
我建议你建立一份个人模板库,不是从网上复制粘贴的文件,而是自己整理、自己注释的文档。格式可以很简单,Markdown或者本地代码文件都行。关键是分类清晰:
- 基础篇:快读、GCD、快速幂、素数筛
- 数据结构篇:并查集、树状数组、线段树(可选)
- 搜索篇:DFS、BFS、回溯
- 图论篇:Dijkstra、Kruskal、拓扑排序
- DP篇:背包、LIS、区间DP
- 技巧篇:二分答案、双指针、前缀和、差分
每次刷题遇到一个能补充进模板库的特殊用法,就把它加进去,并在旁边标注“解决哪道题时用过”。这个动作会让模板库越来越贴合你的思维习惯。到了后期,你甚至可以给每个模板写一段“什么时候用、什么时候别用”的笔记,这才是模板库真正的灵魂。
5.3 考场上最容易翻车的四个点
最后说几个我在真实比赛和模拟赛中反复踩过的坑。每个都值得你贴在自己的电脑前。
- 初始化遗漏。比如并查集的init、树状数组的tree清零、差分数组的diff长度开小了。比赛开始后先花30秒检查全局变量是否需要初始化。
- 数组越界。蓝桥杯题目习惯把数组下标从1开始计数,你如果按0开始写,边界极易多一脚少一脚。我的习惯是数组长度固定为MAXN+5,宁浪费几字节,不冒险越界。
- 类型溢出。窗口和、路径长度、方案数,很多数值看似不大,但加上乘法或累加就可能爆int。让我说句不中听的:蓝桥杯比赛里用long long的容错率,远高于你省那点时间的收益。
- 样例过了就交。样例只能说明程序能跑通,不能说明正确。如果时间允许,自己造几组小数据去验证边界条件,尤其是数组长度1、空区间、全相等元素这些极端情况。我见过太多人样例一过就兴冲冲交上去,最后因为没处理
n=1这种边界拿了0分。
如果你能做到“看到题目,先判断模型,再对应模板,最后处理边界”,蓝桥杯的分数上限会明显提高。模板不是捷径,它只是把“重复劳动”从比赛时挪到了比赛前,让你在赛场上把精力留给真正需要思考的设计部分。从今天开始,选一份模板,用我说的默写法练起来,两周之后你会发现自己敲代码的底气和速度完全不一样。
