- 第 204 篇 -
Date: 2026 - 04 - 09~10
Author: 郑龙浩(仟墨)
1 两日复盘计划
所有题,只回顾,不重做
2026-04-09:
- 1 DFS BFS 孤岛题型(6个题)
- 2 DFS BFS 专项训练(20个题,只回顾不重做)
- 3 第16届蓝桥杯真题(10个题)
- 4 什么情况用DFS 什么情况用BFS,BFSDFS使用场景是什么
- 图的遍历,图的基础,邻接矩阵,邻接表
- 二叉树-之前一直没怎么练习的地方(我发现来不及补充知识点了,学了一半放弃了)
- 枚举 专项训练
- 第13届 蓝桥杯真题(10个)
- 蓝桥杯可以使用的DEVC++的技巧
2026-04-10:
- 大致回顾昨天看过的DFSBFS题型
- 二分 回顾
- 记忆STL常用函数 & 容器
- 记忆C语言中常用函数
- 蓝桥杯可以使用的DEVC++的技巧 & DEVC++配置
- 各种数:质数、回文数、...
- 可能会考到的数学知识点,比如弧长公式什么的
- 最大公约数最小公倍数等一类基础问题
- 高精度加减乘除
- 什么情况用普通数组,什么情况用vector
- 基础DP,基础01背包,基础完全背包类题型
- 并查集
- 双指针
- 前缀和差分
- 单调栈(5个题)一直忘记练习这个了,就看五个题有个印象好了
- 阅读蓝桥杯线下比赛手册注意事项,看比赛环境使用说明
2 什么情况下使用DFS,什么情况下使用BFS
用 DFS 的情况:
- 枚举所有解/可能性:全排列、组合、子集、数独、N皇后。
- 图遍历与统计 :
- 遍历:不要求步数最少,只需走一遍所有连通点。
- 统计:数连通块个数、求各连通块大小、判断二分图(染色法)、求有向图环(回溯+状态判断)
- 树问题:先/中/后序遍历,求深度、求字数大小、路径和。
- 路径记录:迷宫所有路径、约束条件下的可行路径、需要保存选择序列
- 只问存在性:能否到达/完成(不求最短步骤)。
用 BFS 的情况:
- 求最短路径/最少不熟(无权图):无权图的最短路径、二维网格最少步数、单词接龙最短序列、迷宫最少步数
- 层次遍历或按层扩展:树按层输出、社交网络n度好友、病毒扩散模拟、状态转移的最小步数
- 避免递归过深导致栈溢出:BFS 常用队列,迭代进行 --> 用BFS替代DFS --> 当解空间深度大但广度小
- 某些状态转移的最优解问题(如 0-1 BFS 或带权 BFS 的变形)。
关于图的遍历与统计使用 DFS BFS:
DFS:拓扑序、连通性、环检测
BFS:最短路径、距离相关
为什么有权图不能使用BFS?
BFS 是按"层"(步数)扩展的,但"最短步数"≠"最短路径权值和"
- BFS 看的是"步数"(边数),不是边权总和
- 如果题求的是权值最大路径,DFS可以通过回溯去找,而BFS无法进行回溯
3 图 & 图的存储方式
- 图 :由节点 和连接节点的边 构成。可以是有向图 (边有方向)或无向图 (边无方向)。可以为加权图(边有权值)。
- 度 :
- 无向图 :连接节点的边数即为该节点的度 。
- 该节点连接着几个边,该节点就有几度
- 有向图 :分为出度 (从该节点出发的边数)和入度 (指向该节点的边数)。
- 指向该节点的边数为3,入度就是3
- 无向图 :连接节点的边数即为该节点的度 。
- 连通性 :
- 连通图(无向):图中任意两节点均可相互到达。
- 强连通图(有向) :图中任意两节点均可相互到达(注意方向性,比无向图要求更严格)。
- 连通分量 :
- (无向图的)连通分量 :一个极大连通子图,所有节点都连接起来的图内部的子图。简单来说,它是一个内部所有节点都能通过路径互相到达的、无法再扩大的节点集合及其关联边。
- (有向图的)强连通分量 :有向图中的一个极大强连通子图,所以节点都连接起来的子图,且是有方向的。如果节点a->b,则b不能到a
-
朴素存储 :简单存储所有边的列表(如
[起点, 终点]的数组)(一般不用)- 优点:直观,易于构建。
- 缺点:查询任意两节点是否相连效率低(需遍历),不便于进行深度/广度优先搜索。
-
邻接矩阵 :使用
n x n的二维数组(n为节点数)表示。matrix[i][j]的值表示从节点i到节点j的边(在加权图中可存储权值,在无向图中需同时设置matrix[i][j]和matrix[j][i])。- 优点 :查询任意两节点间是否有边极快(O(1))。
- 缺点 :空间复杂度高 (O(n²)),在稀疏图(边数远小于n²)中会造成大量空间浪费。遍历某节点的所有邻居需要扫描整行,在稀疏图中时间效率低。
-
邻接表 :使用"数组+链表"的结构。数组下标对应节点,每个数组元素是一个链表(或可变长数组),用于存储该节点的所有邻居节点(在加权图中可同时存储边权)。
- 优点 :空间效率高,尤其适合稀疏图,仅存储存在的边。遍历某节点的所有邻居非常高效。
- 缺点 :查询任意两节点
i和j之间是否有边,需要遍历i或j的邻居链表,效率低于邻接矩阵(O(k),k为邻居数)。
4 二叉树
我把二叉树这块忘记了,忘记练习二叉树的题了,一直没练这块内容,简单看点基础好了
1前中后序
根左右,左右根,左根右什么的我不太理解,按照下面的方式理解的
-
前序遍历:第一次到达这个节点时(刚进入这个节点)就打印。
-
中序遍历:从左子树完全返回后(准备去右子树之前)打印。
- 就是左下方没有节点了,就返回,然后往右下走
-
后序遍历:从右子树完全返回后(即将离开这个节点)打印。
- 同理
关键在于 :每次到一个节点,会有三次机会处理它:
- 前序遍历:第一次访问节点时打印
- 中序遍历:第二次访问节点时打印
- 后序遍历:第三次访问节点时打印
2 二叉树的定义
cpp
struct TreeNode {
int val; // 存储节点值的整型变量
TreeNode *left; // 指向左子节点的指针
TreeNode *right; // 指向右子节点的指针
// 构造函数
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
3 递归遍历
我看了遍历代码后才意识到,根左右,左根右,左右根是指的写递归函数的顺序呀
cpp
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
}
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
vec.push_back(cur->val); // 中
traversal(cur->right, vec); // 右
}
cpp
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
vec.push_back(cur->val); // 中
}
2026-04-10
1 各种容器
cpp
/* 2026-04-10-蓝桥杯前一天各种小练习
* 1-各种容器
* Author:郑龙浩 Date:2026-04-10
*/
#include <bits/stdc++.h>
#include <set> // 必须
#include <vector> // 如果用了vector
#include <iostream> // 如果用了cout
using namespace std;
typedef long long ll;
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
// 1 vector
vector <int> nums = {1, 2, 3, 4, 5};
nums.resize(6, 6); // 改为6长度,且增加的位置初始化为6
nums.insert(nums.begin() + 2, 1); // 将1插入nums[2]的位置,且nums[2]以及往后向后平移
for (int i : nums) cout << i << ' ';
for (auto &it : nums) cin >> it; // 这样输入要加&
// 2 stack
stack<int> st;
for (int i = 0; i < 10; i) st.push(i);
while (!st.empty()) {
int tp = st.top(); // 栈顶元素
st.pop();
}
// 手动模拟板
int sta[100];
int pos = -1; // 没元素,栈顶为-1
for (int i = 0; i <= 5; i++) cin >> sta[++pos];
int element = sta[pos--]; // 出栈
// 3 queue priority_queue <int> q
// 优先队列
// priority_queue<数据类型, 底层容器, 比较器>
// ↑ ↑ ↑ ↑
// 优先队列 存什么类型 用什么装 怎么排序
// 1)最简单写法(大顶堆)
priority_queue<int> q1; // 省略了后两个参数
// 2)完整写法(大顶堆)
priority_queue<int, vector<int>, less<int>> q2;
// 3)小顶堆(最常用!)
priority_queue<int, vector<int>, greater<int>> q3;
// 4)复杂类型(比如pair)
// 默认先对 pair 的 frst 进行降序排序,然后再对 second 降序排序 对 frst 先排序,大的排在前面,
// 如果 frst 元素相同,再对 second 元素排序,保持大的在前面
priority_queue<pair<int, int>, vector<pair<int, int>>> q4;
// 存pair,用vector<pair>做容器,从小到大排序
priority_queue<pair<int, int>, vector<pair<int, int>>, greater <>> q5;// 这里greater中间不需要加pair<int, int>
// 4 map
map <int, int> Map;
Map.insert({1, 2}); Map.insert({2, 3}); Map.insert({4, 2});
Map[5] = 10; Map[6] = 100;
// 4.1 查找key可以使用的方法
int key = 2;
Map.find(key); // 返回的是迭代器,不是key
Map.count(key); // 查看key是否存在,存在返回1,不存在返回0
Map[key]; // 不存在key时,会自动创建
// 4.2 遍历
for (auto it : Map) cout << it.first << ' ' << it.second;
// for (auto it = Map.begin(); it < Map.end(); it++) cout << it.first << ' ' << it.second; // 错误
for (auto it = Map.begin(); it != Map.end(); it++) cout << it->first << ' ' << it->second;
for (auto it = Map.begin(); it != Map.end(); it++) cout << (*it).first << ' ' << (*it).second;
for (auto &p : Map) cout << p.first << " -> " << p.second << endl;
// 逆向遍历
for (auto it = Map.rbegin(); it != Map.rend(); it++) cout << it->first << ' ' << it->second;
// 4.3 二分查找
Map.lower_bound(key); // 返回一个迭代器,指向键值>= key的第一个元素
Map.upper_bound(key); // 返回一个迭代器,指向键值> key的第一个元素
// 4.4 删除
Map.erase(key); // 根据映射的键删除键和值
auto first = Map.find(key), second = Map.find(key + 5);
Map.erase(first, second); // 删除左闭右开区间迭代器对应的键和值(first second 必须是迭代器)
// 5 unordered_map 与 multiamp
// unordered_map 与 set :
// 随着内部元素越来越多,两种容器的插入删除查询操作的时间都会逐渐变大,效率逐渐变低。
// 使用 [] 查找元素时,如果元素不存在,两种容器都是创建一个空的元素;如果存在,会正常索引对应的值。
// 所以如果查询 过多的不存在的元素值,容器内部会创建大量的空的键值对,后续查询创建删除效率会大大降低。
if (Map.count(key)) cout << Map[key];
// multiamp: 键可以重复,一个键对应多个值,且有序出现的
// 6 priority set map 改变排序
// 6.1 priority
// 默认:大顶堆
priority_queue<int> pq1; // 从大到小
// 改为小顶堆
priority_queue<int, vector<int>, greater<int>> pq2; // 从小到大
// 或用 greater<>(C++11)
priority_queue<int, vector<int>, greater<>> pq3;
// 6.2 set 不能使用greater<> 需要指定类型
// 默认:从小到大
set<int> s1 = {3, 1, 4, 2};
// s1: 1 2 3 4
// 改为从大到小
set<int, greater<int>> s2 = {3, 1, 4, 2};
// s2: 4 3 2 1
// 6.3 map
map<int, int, greater<int>> m; // 指定key的类型
// 7 string
string s = "abcddefg";
// 7.1 比较
// "abc" < "def";
// string s1 = "abc", s2 = "abc";
// s1 == s2; // true
// s1.compare(s2); // 0表示相等
// 7.2 拼接
// s1 = "hello";
// s2 = "world";
// string s3 = s1 + " " + s2; // string + 字面量
// string s4 = s1 + s2; // string + string
// s1.append("!"); // 追加字符串
// s1 += "!!"; // 追加字符或字符串
// 7.3 插入
s = "abcddefg";
s.insert(s.begin() + 1, 'v'); // 在位置1插入字符'v'
s.insert(3, "XXX"); // 在位置3插入字符串"XXX"
s.push_back('z'); // 末尾插入字符
s.append("123"); // 末尾插入字符串
// 7.4 删除
s = "abcdefg";
s.erase(s.begin() + 1); // 删除位置1的字符
s.erase(1, 3); // 从位置1开始删除3个字符
s.erase(s.begin() + 1, s.begin() + 4); // 删除[1,4)范围的字符
s.pop_back(); // 删除最后一个字符
s.clear(); // 清空整个字符串
// 7.5 查找
// 找不到返回 -1
s = "hello world";
size_t pos = s.find("lo"); // 查找子串,返回位置
pos = s.find("lo", 3); // 从位置3开始查找
pos = s.rfind("o"); // 从后往前查找
string s = "012efghkki";
s.find_first_of("aeiou"); // 查找"aeiou"中第一个属于s的字符 3--e
s.find_last_of("aeiou"); // 查找"aeiou"中最后一个属于s的字符 9--i
// 7.6 子串
s = "hello world";
string sub = s.substr(6); // 从位置6到结尾: "world"
sub = s.substr(0, 5); // 从位置0取5个字符: "hello"
sub = s.substr(6, 5); // 从位置6取5个字符: "world"
// 7.7 替换
s = "hello world";
s.replace(6, 5, "there"); // 从位置6开始替换5个字符为"there"
// 7.8 其他常用操作
s = " hello world ";
transform(s.begin(), s.end(), s.begin(), ::toupper); // 转大写
transform(s.begin(), s.end(), s.begin(), ::tolower); // 转小写
reverse(s.begin(), s.end()); // 反转字符串
sort(s.begin(), s.end()); // 排序字符
// 删除空白
s.erase(0, s.find_first_not_of(" ")); // 删除开头空白
s.erase(s.find_last_not_of(" ") + 1); // 删除结尾空白
// 字符串分割
string str = "apple,banana,orange";
vector<string> tokens;
size_t start = 0, end = 0;
while ((end = str.find(',', start)) != string::npos) {
tokens.push_back(str.substr(start, end - start));
start = end + 1;
}
tokens.push_back(str.substr(start));
// 字符串转数字
string num_str = "123";
int num_int = stoi(num_str);
long num_long = stol(num_str);
double num_double = stod("3.14");
// 数字转字符串
int n = 123;
string str_int = to_string(n);
double d = 3.14;
string str_double = to_string(d);
return 0;
}
2 STL函数
cpp
/* 2026-04-10-蓝桥杯前一天各种小练习
* 2-STL函数
* Author:郑龙浩 Date:2026-04-10
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
// 1、二分函数
// 1.1 lower_bound 与 upper_bound
// 如果未找到,返回尾地址的下一个位置的地址
int nums[5] = {1, 1, 2, 2, 6};
vector <int> nums2 = {1, 1, 2, 2, 6};
lower_bound(nums, nums + 5, 2); upper_bound(nums, nums + 5, 2);
lower_bound(nums2.begin(), nums2.end(), 2); upper_bound(nums2.begin(), nums2.end() + 10, 2);
// 2 找最大最小值
// 2.1 返回数组中的最值,返回的是地址,所以需要*
int Max = *max_element(nums, nums + 5);
int Max2 = *max_element(nums2.begin(), nums2.end());
int Min = *min_element(nums, nums + 5);
int Min2 = *min_element(nums2.begin(), nums2.end());
// 2.2
int Ma = max(1, 4); int mi = min(1, 8);
int M = max({1, 2, 3, 4, 6}); int M2 = min({1, 2, 3, 4, 5, 6});
// 2.3 minmax()
pair <int, int> it = minmax(1, 4);
// 2.4 minmax_element()
// 返回序列中最小和最大值的地址组成的pair,也就是pair中存储了两个地址,而不是两个值
auto MinMax = minmax_element(nums2.begin(), nums2.end());
int MaxNum = *(MinMax.first); int MinNum = *(MinMax.second);
// 3 第n小的元素放在nth上 第一次知道
// 将序列中第3小的元素放到了nums2[2]上,且nums2[2]前面元素都小于nums2[2],后面元素大于nums2[2]
nth_element(nums2.begin(), nums2.begin() + 2, nums2.end());
// 4 求序列的下一个排列 前两天刚用到,但是记不住名字
vector <int> arr = {1, 2, 3, 4 ,5};
next_permutation(arr.begin(), arr.end());
// 注意:如果是最后一个排列返回false,否则true
// 5 partial_sort()
// be 到 be + 3排序
partial_sort(arr.begin(), arr.begin() + 3, arr.end());
// 降序可以加greater <int> ()
// 6 序列逆转
int a[5] = {1, 2, 3, 4, 5};
reverse(a, a + 5);
string s = "sdada";
reverse(s.begin(), s.end());
// 7 stoi string -> int
string s = "1234";
int num = stoi(s);
// 8 to_string 数字 -> string
int a1 =12344;
string s = to_string(a1);
// 8 最大公约数 最小公倍数
int aa = __gcd(12, 15); // --> 3
int bb = 12 * 15 + aa; // 最小公倍数是 aa * bb 再加上两者公因数
return 0;
}
3 C语言中的函数
cpp
/* 2026-04-10-蓝桥杯前一天各种小练习
* 3-C语言中的函数
* Author:郑龙浩 Date:2026-04-10
*/
#include <stdio.h> // 1. 输入输出
#include <stdlib.h> // 2. 转换、动态内存
#include <string.h> // 3. 字符串、内存
#include <math.h> // 4. 数学
#include <time.h> // 5. 时间
#include <ctype.h> // 6. 字符处理
#include "bits/stdc++.h"
using namespace std;
int main() {
/* ========== 1. 输入输出函数 (stdio.h) ========== */
// 1.1 格式化输入输出
int a; double b; char c, s[100];
scanf("%d%lf %c%s", &a, &b, &c, s);
printf("a=%d, b=%.2f, c=%c, s=%s\n", a, b, c, s);
// 1.2 快速读入函数(竞赛常用)
int fast_read() {
int 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;
}
/* ========== 2. 内存操作函数 (string.h) ========== */
// 2.1 memset - 内存设置
int arr1[100];
memset(arr1, 0, sizeof(arr1)); // 全0
memset(arr1, -1, sizeof(arr1)); // 全-1
memset(arr1, 0x3f, sizeof(arr1)); // 设为无穷大
// 2.2 memcpy - 内存拷贝
int src[5] = {1,2,3,4,5}, dest[5];
memcpy(dest, src, sizeof(src)); // 完整拷贝
memcpy(dest, src, 3 * sizeof(int)); // 部分拷贝
// 2.3 memcmp - 内存比较
int cmp = memcmp(src, dest, 5 * sizeof(int));
/* ========== 3. 字符串函数 (string.h) ========== */
// 3.1 基本操作
char str1[50] = "hello", str2[50] = "world";
int len = strlen(str1); // 长度
strcpy(str2, str1); // 复制
strcat(str1, " world"); // 拼接
// 3.2 比较
int str_cmp = strcmp(str1, str2); // 比较
// 3.3 查找
char *p1 = strstr(str1, "ell"); // 查找子串
char *p2 = strchr(str1, 'e'); // 查找字符
/* ========== 4. 数学函数 (math.h) ========== */
// 4.1 基本运算
int abs_val = abs(-5); // 绝对值
double fabs_val = fabs(-3.14); // 浮点绝对值
double pow_val = pow(2.0, 3.0); // 幂运算
double sqrt_val = sqrt(16.0); // 平方根
// 4.2 取整
double ceil_val = ceil(3.2); // 向上取整
double floor_val = floor(3.8); // 向下取整
double round_val = round(3.5); // 四舍五入
// 4.3 三角函数(弧度制)
double sin_val = sin(3.14159/2); // 正弦
double cos_val = cos(3.14159); // 余弦
double tan_val = tan(3.14159/4); // 正切
// 4.4 对数指数
double log_val = log(10.0); // 自然对数
double log10_val = log10(100.0); // 常用对数
double exp_val = exp(1.0); // e的幂
/* ========== 5. 字符处理函数 (ctype.h) ========== */
char ch = 'A';
// 5.1 判断
int is_alpha = isalpha(ch); // 是否字母
int is_digit = isdigit(ch); // 是否数字
int is_lower = islower(ch); // 是否小写
int is_upper = isupper(ch); // 是否大写
int is_space = isspace(ch); // 是否空白
int is_alnum = isalnum(ch); // 是否字母或数字
// 5.2 转换
char lower_ch = tolower('A'); // 转小写
char upper_ch = toupper('a'); // 转大写
/* ========== 6. 转换函数 (stdlib.h) ========== */
// 6.1 字符串转数值
int int_val = atoi("123"); // 字符串转int
long long_val = atol("123456"); // 字符串转long
double double_val = atof("3.14"); // 字符串转double
// 6.2 数值转字符串
char buffer[50];
sprintf(buffer, "%d", 123); // int转字符串
sprintf(buffer, "%.2f", 3.14159); // double转字符串
/* ========== 9. 位运算技巧 ========== */
unsigned int x = 10;
int is_odd = x & 1; // 判断奇偶
int half = x >> 1; // 除以2
int doubled = x << 1; // 乘以2
int last_bit = x & (-x); // 获取最后一位1
int remove_last = x & (x-1); // 去掉最后一位1
int is_power_of_two = (x & (x-1)) == 0; // 判断是否为2的幂
/* ========== 11. 常用常量定义 ========== */
const int INF = 0x3f3f3f3f; // 无穷大
const double PI = acos(-1.0); // π
const double EPS = 1e-8; // 浮点误差
return 0;
}