1. 写在最前面这24小时到底该怎么用作为一个参加过好几届蓝桥杯、也带过不少学弟学妹临时抱佛脚的人我先说句实话如果你只剩一天别指望学会所有的算法但一定要把模拟、set、map这三个词焊死在脑子里。它们是蓝桥杯省赛里最基础、最常考、也最容易被忽视的得分点。标题里说的第一天其实是指你从零开始准备的第一天也可能是你赛前最后能高效利用的一天这两者我都经历过所以这篇总结会尽量按要命的重点来排优先级。先说结论模拟题的核心价值不是暴力两个字这么简单它是你读懂题面、建立代码手感、稳住心态的基石。而set和map是处理去重、统计、映射关系这三类高频需求的利器。如果你C语法还不熟或者Python基础一般这篇文章就是给你准备的读完照着敲一遍比刷十道难题有用得多。这一天的安排建议是这样的上午把set和map的基本操作过一遍下午找三道经典模拟题练手晚上把这两者结合起来做几道综合题。不用贪多重点是把什么时候该用set/map这种直觉建立起来。下面我按这个节奏来展开。2. 一道真题带你理解模拟题到底在模拟什么很多新手对模拟题有误解觉得它是暴力枚举是没有技术含量的题。实际上蓝桥杯的模拟题考察的是你的规则翻译能力——能不能把一个用自然语言描述的过程按照逻辑原样翻译成代码。这中间最容易出错的不是语法而是题目里的一句话对应代码里的哪几步。我们来看一道经典模拟题这类题在往届省赛中出现过多次统计卡片问题。题面大致是一套卡片有若干种每种卡片有一定数量现在要按顺序组装若干个目标物品每个物品需要消耗特定卡片问最多能组装多少个。很多人的第一反应是这不是很简单吗一个循环就完事了。但实际写的时候卡点往往出现在三处库存怎么存用数组还是map如果卡片的种类编号是连续的整数比如0到n-1数组就够了如果编号是字符串比如red、blue数组就不行必须用map。这是第一个考点。检查顺序是先检查库存再扣减还是先扣减再检查顺序反了库存会变成负数答案就会错。正确做法是模拟每一步之前先判断库存是否足够够了才扣减。终止条件是组装到某一个失败就停还是全部组装完看结果这取决于题面描述是依次组装直到无法继续还是判断能否全部组装。一字之差代码结构完全不同。我把这段踩坑经历写出来是想强调模拟题的代码量不大但每一处细节都可能影响最终答案。读题的时候把题面里的依次直到至少不超过这类词圈出来它们对应的就是循环条件或边界判断。3. 读题训练从题面到代码的翻译思维模拟题读题不能像读小说一样从头到尾扫一遍就动手。我自己的习惯是第一遍读题第二遍圈关键词第三遍在心里跑一遍样例。这三步做完再动手写代码准确率能提升一大截。具体来说题面里最常见的几类关键词和代码的对应关系是这样的题面关键词代码对应常见坑点依次逐个for循环或while循环循环终止条件没设对直到……为止while循环 break条件死循环或提前退出不超过至少最多比较运算符 边界判断边界值归属搞错与第几次前多少个计数变量 数组下标下标从0开始还是从1开始每种各有多少map存计数初始化/递增顺序写错判断是否存在set.count 或 map.find忘记判空或迭代器解引用举个例子题目说按成绩从高到低排名成绩相同的按学号从小到大这就是一个典型的多关键字排序翻译题。你需要把每个学生的信息存成结构体或者tuple然后自定义排序规则。这类题刷过两次之后就能形成条件反射——见到排名就想到sort见到按某某排序就想到自定义比较函数。读题训练的最高境界是读完题面脑子里已经能画出数据流向——输入是什么结构中间经过哪些处理输出是什么格式。这一步做好了写代码只是打字而已。4. 十分钟掌握set的实战用法set的作用一句话就能讲明白有序、自动去重、支持快速查找的集合。它的底层是红黑树平衡二叉搜索树插入、删除、查找的复杂度都是O(log n)n是集合里元素的个数。我见过太多人把set当成高级工具其实它的用法非常直观。先看一段C模板代码照着敲一遍你就会了#include bits/stdc.h using namespace std; int main() { setint s; s.insert(3); s.insert(1); s.insert(3); // 重复插入集合大小不会变 cout s.size() endl; // 输出2 // 遍历set默认升序 for (int x : s) { cout x ; // 输出 1 3 } cout endl; // 查找 if (s.count(3)) { cout 3在集合中 endl; } // 查找边界lower_bound返回第一个 2 的元素迭代器 auto it s.lower_bound(2); if (it ! s.end()) { cout 第一个大于等于2的元素是 *it endl; // 输出 3 } return 0; }如果你用的是Python对应写法是set代码长这样s set() s.add(3) s.add(1) s.add(3) print(len(s)) # 2 # set本身无序需要按序输出时用sorted for x in sorted(s): print(x, end ) print() # 查找 if 3 in s: print(3在集合中)这里必须提醒一个C新手常踩的坑s.count(3)在set里只可能返回0或1因为set里不会有重复元素。但很多人的代码里会用if (s.find(3) ! s.end())来判断这当然也行不过count更简洁。另外不要试图用set的下标访问元素因为set不是随机访问容器没有[]运算符。set最常见的两个使用场景一是去重题目要求输出不重复的元素二是快速判断存在性题目要求查某个元素是否出现过。这两个场景在模拟题里出现频率极高。5. 十分钟掌握map的实战用法map比set更进一步它存储的是键值对也就是一个映射关系。底层同样是红黑树按键自动排序插入、删除、查找也都是O(log n)。map最常见的应用是统计统计某种元素出现的次数统计某个字符串对应了多少个数量。先看C基础写法#include bits/stdc.h using namespace std; int main() { mapstring, int mp; mp[apple] 2; mp[banana] 3; mp[apple] 5; // 覆盖apple变成5 // 遍历按键的升序 for (auto p : mp) { cout p.first p.second endl; } // 判断键是否存在 if (mp.find(apple) ! mp.end()) { cout apple存在值为 mp[apple] endl; } // 键不存在时[]操作符会自动创建并初始化为0 cout mp[cherry] endl; // 输出0但mp里多了一个cherry return 0; }Python中对应的dict用法几乎完全一样唯一要注意的是Python的dict按键不排序如果需要按键排序输出要手动for k in sorted(mp.keys())。关于map我最想强调的一个细节是mp[key]这个操作当key不存在时会默认创建key并把value初始化为0然后再加1。这个行为在统计频次场景下太方便了很多人都靠它写代码。但它在另一类场景下会带来隐蔽的bug——如果你只是想判断key是否存在用mp[key]会莫名创建一个键导致map大小变大。所以判断存在性时永远优先用mp.find(key)或mp.count(key)而不是mp[key]。map还有一个在蓝桥杯里很常用的小技巧配合lower_bound找最近的键。比如你有一个map存储了某些离散点上的值想找第一个大于等于给定值的键直接mp.lower_bound(limit)能返回迭代器效率比遍历整个map快得多。6. 实战案例一map做频次统计set做去重接下来用一道典型真题来串联set和map统计一段文本中每个单词出现的次数并按出现次数降序输出次数相同的按字典序升序输出。这个题如果不用map做起来会很痛苦要把单词放到数组里排序然后手动数连续相同项的个数。但用map可以一步到位mapstring, int cnt; string word; while (cin word) { cnt[word]; }统计完之后需要按出现次数降序若次数相同按字典序升序排序。注意map默认是按key排序的满足不了这个需求所以要把map里的pair搬到vector里自定义比较函数vectorpairstring, int arr(cnt.begin(), cnt.end()); sort(arr.begin(), arr.end(), [](const auto a, const auto b) { if (a.second ! b.second) return a.second b.second; return a.first b.first; });Python版本更简洁from collections import Counter cnt Counter(words) # sorted默认按tuple的第一个元素排序这里用key指定 result sorted(cnt.items(), keylambda x: (-x[1], x[0]))这个例子同时展示了set和map的定位差异统计频次用map去重排序用set两者经常搭配出现。当我需要把所有出现过的单词去重后排序我可以直接把cnt的键取出来丢进set其实map的键本身就已经去重且有序了所以这里并不需要真的用set。但如果你想统计的是不重复的单词有多少种那么cnt.size()一行就能拿到这就是map作为键集合的好处。7. 实战案例二用set的自动有序解决区间合并问题区间合并是蓝桥杯模拟题里一个很有意思的考点也是set大显身手的场景。题目形式通常是给若干区间[L, R]现在需要把重叠的区间合并问合并后剩下多少个区间或者覆盖的总长度是多少。很多人的第一反应是先排序再逐个合并这确实是最正统的做法。这里我想强调的是用set来做排序代码会更自然。因为set天然有序你把所有区间按起点存进set遍历的时候它们自动排好序了根本不用再写一个sort。不过这里有个细节陷阱如果直接setpairint, int s;存区间它会先按区间起点排序再按终点排序这没问题。但如果题目里区间可能有完全相同的你需要去重吗如果题面没说区间各不相同那set会自动去重可能导致正确的合并逻辑被跳过。所以一定要先想清楚题目里区间是否允许重复。如果允许重复就不能简单用set存要改用multiset或者vector sort。合并的核心逻辑是这样的从前往后扫描区间维护当前合并段的起点L和终点R。如果下一个区间的起点小于等于当前段的终点R说明有重叠把一段的终点更新为两者的较大值否则说明这段已经到头了把它记录到答案里然后开启新的合并段。setpairint, int segs; for (int i 0; i n; i) { int l, r; cin l r; segs.insert({l, r}); } int cnt 0; int curL -1, curR -1; for (auto p : segs) { if (curL -1) { curL p.first; curR p.second; cnt 1; } else if (p.first curR) { curR max(curR, p.second); } else { cnt; curL p.first; curR p.second; } } cout cnt endl;这个例子是想告诉你set不只是去重容器它的有序性本身就是一种算法资源省掉了很多排序代码。当然如果你对sort足够熟悉用vector排序也没有任何问题set只是一个选择不是必须。8. 实战案例三map建立身份映射的经典模型模拟题里有一种常见的给物品编号/给名字编号的套路非常值得掌握。题目大致是有一批订单每个订单包含一个商品名和一个数量最后要求按商品的第一个出现顺序或字典序汇总每种商品的销量。这种情况下map有两个用途一是用mapstring, int sum;直接累加数量非常简单二是如果题目要求按输入顺序输出那map就不够用了因为map会自动按键排序而不是输入顺序。这时候你需要保留一个vector记录第一次出现的顺序同时用map判断这个商品是否已经出现过。这里有一个非常经典的技巧用map的count方法判断是否第一次出现同时维护一个vector记录顺序。mapstring, int quantity; vectorstring order; for (auto it : records) { if (quantity.count(it.name) 0) { order.push_back(it.name); // 第一次出现记录顺序 } quantity[it.name] it.num; } for (auto name : order) { cout name quantity[name] endl; }这个模式我愿称之为map借壳vector保留时间顺序在蓝桥杯中出现的频率非常高。如果你不这样做而是想着等全部统计完再按输入顺序排一次还得额外记录每个商品第一次出现的下标反而更麻烦。还有一种变体是给每个名字分配一个编号再用数组存信息。这适合名字数量比较少的场景比如最多1e5个不同名字。map负责把名字映射成int编号然后你所有统计都可以用int数组完成速度更快写起来也跟数组操作一样简单。这就是map最常见的使用场景——字符串无法做数组下标用map翻译成整数下标。9. set和map的底层原理为什么说它们稳很多教程会告诉你怎么用set/map但不告诉你它们为什么快、什么时候慢。理解了底层原理你写代码时才不会在数据规模上翻车。C里的set和map底层都是红黑树这是一种自平衡的二叉搜索树。插入、删除、查找的复杂度都是O(log n)。这意味着什么如果你有1e5个元素log2(1e5)大约是17每次操作不到20步非常快。如果n是1e6log2约为20依然很快。在蓝桥杯的数据规模下通常1e5到1e6set/map的O(log n)完全足以应付。但要警惕两个慢的地方红黑树的常数因子比数组大。数组的O(1)访问是真的快而set/map的insert/erase/find都涉及指针跳转和树旋转常数大约是数组操作的好几倍。所以如果数据是连续的整数且范围不大用数组远优于set/map。哈希版本的set/map即unordered_set/unordered_map平均O(1)但不保证顺序。如果你只需要快速查找、不需要有序性用它们更快。但蓝桥杯的有些题要求输出有序这时候无序容器就要配一个sort综合下来不一定划算。Python这边的情况类似set和dict的底层是哈希表平均O(1)但遍历时无序sorted()需要额外O(n log n)。C的set/map反而自带有序。所以选型时要结合题目要求题目要求有序输出优先C的set/map只要求快速查询用哈希容器或Python set/dict更香。10. 从“会用”到“混分”模拟题set/map的组合拳策略如果你只剩一天想靠模拟题set/map拿分最快的方法不是刷难题而是做组合题——即一道题里同时考察模拟过程和数据结构的选型。我把自己总结的混分策略分享给你可以直接作为这一天的行动指南。第一步把set/map的模板代码背熟。不需要背API文档但insert、count、find、erase、[]、遍历这几个操作必须能闭着眼睛写出来。我见过太多人考场上卡在这个函数返回的是迭代器还是bool这种细节上白白浪费半小时。第二步精做三道经典模拟题。每题做完后问自己三个问题这题翻译成代码的关键点是什么用到了哪个数据结构为什么是它而不是数组如果我换一种数据结构写会不会更简单这三个问题能帮你建立题型-数据结构的直觉。相信我这种直觉是考场上最值钱的东西。第三步刻意练习边模拟边用map记录状态的场景。很多问题看似是在模拟某个过程但过程中需要不断记录当前的状态量这个状态量通常就是一串键值对。比如模拟股票买卖的持仓记录你需要知道当前有哪些股票、各持有多少股用map存股票名-数量最合适。再比如模拟一个游戏的角色背包背包里不同物品有几个依然是map的活。第四步考前半小时看一眼自己整理的错误清单。比如用mp[key]判断存在性导致误插入了默认值set迭代时同时删除元素没注意迭代器失效这类坑看一遍能帮你避开大量低级失误。11. 常见坑点清单set/map使用中的高频翻车点这里我汇总了在刷题和实际比赛中遇到最多的几个坑建议直接截图备忘。坑1迭代器失效。C里set/map的迭代器在插入、删除其他节点时不会失效这一点比vector友好。但如果你在遍历时删除了当前迭代器指向的元素紧接着又对同一个迭代器做行为是未定义的。正确做法是用it container.erase(it)这条语句会返回下一个元素的迭代器。记住它你的代码就能安全地边遍历边删除。for (auto it s.begin(); it ! s.end(); ) { if (*it % 2 0) { it s.erase(it); } else { it; } }坑2误以为map会自动按value排序。map按键排序是特性按value排序不是map支持的。你需要把pair搬到vector里再sort就像第6节那样。很多人在按次数排序的题上折戟就是因为没绕出这个弯。坑3多次使用mp[key]导致意外创建键。前面说过mp[key]在键不存在时会创建键。如果代码中先用了mp[key]做判断后面再做统计map的大小可能比预期大。排查这种问题时可以临时打印mp.size()看它是否比实际种类数多。坑4set/map的插入操作比较慢如果重复插入大量重复元素性能浪费。如果你能提前知道要插入的元素有很多重复可以先用vector收集最后一次性去重。但大多数情况下set/map的O(log n)已经够用不必过度优化。坑5Python的set/dict无序输出顺序不对。Python里set的遍历顺序是哈希表的存储顺序看起来无规律。需要按顺序输出时一定记得先用sorted()或dict.items()的排序操作。坑6读题时没注意是否有序的要求。题目如果要求按输入顺序输出set/map的有序性反而会成为障碍此时必须配合vector记录顺序第8节的map借壳vector模式。读题时把按字典序按时间顺序按编号从小到大这类词都圈出来。12. 最后一天的执行建议与做题顺序我知道看到这里的人多半已经有点焦虑了。剩一天时间到底怎么把这篇文章的价值最大化我的建议是不要再碰完全没接触过的算法只把模拟setmap练熟。蓝桥杯省赛并不是只有难题有相当一部分分值来自能读懂题就能做的题目。你把这类题拿下再随缘做一道基础贪心/动态规划拿奖的概率已经不小了。具体的做题顺序我建议按照套模板-做真题-错题复盘三步走套模板把第4节、第5节的代码自己敲一遍不看书默写核心操作。做真题给自己定2小时做三道模拟题可以找往年的省赛题严格模拟比赛环境不开外部资料。错题复盘把每道错题的卡点写下来对照第11节的坑点清单找到自己最容易犯的错误类型。如果你还有额外的时间就把区间合并频次统计身份映射这三种场景再各做一遍。这基本上是模拟题set/map的最高频组合练熟了省赛的保底分就到手了。最后再分享一个心态上的经验蓝桥杯的题面经常很长有很多干扰信息。模拟题尤其喜欢包装把简单的逻辑藏在花哨的背景故事里。遇到这种题别慌先忽略故事把输入、输出和操作规则抽出来翻译成数据结构。你手里握着set和map这两件工具大部分计数、去重、映射的场景根本逃不出你的手心。