【递归】
算法模板
void fun(x)
{
if (..)
{
return; // 递归退出条件
}
fun(y); // 数据拆分
}
算法应用
【DFS - 一维】
算法模板
void dfs(int u)
{
if (u==n)
{ // 到达n层后结束搜索
for (int i=0; i<n; i++)
{
// 按照题目要求执行,或者打印、计算等
}
return ; // 一定要加这句返回
}
// 注意:DFS没有通用模板,每道题都有自己的顺序,这里需要根据每道题进行修改
for (int i=1; i<=n; i++)
{ // 每个位置可以遍历的内容,如这里是可以填入1~n的数字
if (!st[i])
{ // 判断状态,确定是否可以填入
path[u] = i;
st[i] = true;
dfs(u+1); // DFS搜索
// path[u] = 0; // 可以不要这句,因为每次都被赋值
st[i] = false;
}
}
}
// 从第0层开始遍历
dfs(0);
// 剪枝方法1
// 增加sum参数,直接传递当前已选数的总和,避免重复计算,从而提高效率
void dfs(int step, int sum)
// 剪枝方法2
// 只要满足一种方案就退出
void dfs(int step)
{
if (flag) return;
}
算法应用
【DFS - 二维】
算法模板
算法应用
【BFS - 一维】
算法模板
// 该模板存在问题是队列长度只增不减,可使用vector方式保持队列长度始终在范围内
typedef pair<int, int> PII; // 三个及以上的参数适合用结构体
PII q[N*N];
int d[N][N]; // 记录每个点到起点的距离
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int bfs()
{
int hh=0, tt=0;
q[0] = {0, 0};
memset(d, -1, sizeof(d)); // 初始化每个点到起点的距离
d[0][0] = 0; // 起点的距离为0
while (hh<=tt)
{
auto t = q[hh++];
for (int i=0; i<4; i++)
{
int x = t.first+dx[i], y = t.second+dy[i];
if (x>=0 && x<n && y>=0 && y<m && g[x][y]==0 && d[x][y]==-1)
{
d[x][y] = d[t.first][t.second]+1;
q[++tt] = {x,y};
}
}
}
return d[n-1][m-1];
}
bfs() // 调用bfs(),返回最短路径
算法应用
【BFS - 二维】
算法模板
单源BFS
// 用vector改写
typedef pair<int, int> PII; // 三个及以上的参数适合用结构体
queue<PII> q;
int d[N][N]; // 记录每个点到起点的距离,这里无需vis数组(dist[][]==-1表示没走过)
int dx[4]={-1, 1, 0, 0};
int dy[4]={0, 0, -1, 1};
void bfs()
{
memset(d, -1, sizeof(d)); // 初始化每个点到起点的距离
q.push({0,0});
d[0][0] = 0; // 起点的距离为0
while (!q.empty())
{
int x = q.front().first, y = q.front().second;
q.pop();
for (int i=0; i<4; i++)
{
int nx = x+dx[i], ny = y+dy[i];
if (nx<1 || nx>n || ny<1 || ny>n || g[nx][ny]==1 || d[nx][ny]!=-1)
continue;
d[nx][ny] = d[x][y]+1;
q.push({nx, ny});
}
}
}
bfs() // 调用bfs(),dist[i][j]中保存着最短路
多源BFS
// 与单源BFS不一样的地方在与需要将多个值压入队列
void bfs()
{
// ...
for (int i=1; i<=cur; i++)
q.push(a[i]);
// ...
}
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| AcWing |
173 矩阵距离 |
|
|
|
|
| AcWing |
175 电路维修 |
|
|
|
|
| AcWing |
188 武士风度的牛 |
|
|
|
|
| AcWing |
844 走迷宫 |
简单 |
⭐⭐ |
BFS(广度优先搜索) |
用队列从起点 \((1,1)\) 开始逐层扩展,每次取出队首节点后遍历上下左右四个方向(dx[4]={-1,0,1,0}、dy[4]={0,1,0,-1}),若新坐标在边界内、不是障碍、未访问(d[x][y]==-1)则更新距离 d[nx][ny]=d[x][y]+1 并入队;数组模拟队列时 hh 为头指针、tt 为尾指针,d 数组同时作为距离和访问标记(初始化为 -1);BFS 第一次到达终点 \((n,m)\) 时的距离即为最短路径,时间复杂度 \(O(n \times m)\)。 |
| AcWing |
845 八数码 |
|
|
|
|
| AcWing |
1076 迷宫问题 |
|
|
|
|
| AcWing |
1096 地牢大师 |
|
|
|
|
| AcWing |
1097 池塘计数 |
|
|
|
|
| AcWing |
1098 城堡问题 |
|
|
|
|
| AcWing |
1099 仙岛求药 |
|
|
|
|
| AcWing |
1101 献给阿尔吉侬的花束 |
|
|
|
|
| AcWing |
1102 移动骑士 |
|
|
|
|
| AcWing |
1106 山谷和山峰 |
|
|
|
|
| AcWing |
1107 魔板 |
|
|
|
|
| AcWing |
3205 最优配餐 |
|
|
|
|
| AcWing |
3675 逃离迷宫 |
|
|
|
|
| AcWing |
5977 最少步数 |
|
|
|
|
| AcWing |
5978 走迷宫 |
|
|
|
|
| AcWing |
5979 走出迷宫 |
|
|
|
|
| 洛谷 |
AT_abc383_c Humidifier 3 |
普及- |
|
|
|
| 洛谷 |
P1162 填图颜色 |
普及- |
|
|
|
| 洛谷 |
P1596 Lake Counting |
普及- |
|
|
|
| 洛谷 |
[[P1746 离开中山路]] |
普及- |
|
|
|
| 洛谷 |
[[P2873 Mud Puddles]] |
普及- |
|
|
|
| 洛谷 |
[[P2958 Papaya Jungle]] |
普及- |
|
|
|
| 洛谷 |
U483711 最大黑区域 |
普及- |
|
|
|
| 洛谷 |
AT_abc387_d Snaky Walk |
普及 |
|
|
|
| 洛谷 |
AT_abc400_d Takahashi the Wall Breaker |
普及 |
|
|
|
| 洛谷 |
AT_abc405_d Escape Route |
普及 |
|
|
|
| 洛谷 |
AT_abc407_d Domino Covering XOR |
普及 |
|
|
|
| 洛谷 |
AT_abc420_d Toggle Maze |
普及 |
|
|
|
| 洛谷 |
AT_abc425_d Ulam-Warburton Automaton |
普及 |
|
|
|
| 洛谷 |
AT_abc436_d Teleport Maze |
普及 |
|
|
|
| 洛谷 |
P1443 马的遍历 |
普及 |
|
|
|
| 洛谷 |
[[P1747 好奇怪的游戏]] |
普及 |
|
|
|
| 洛谷 |
P1825 Corn Maze |
普及 |
|
|
|
| 洛谷 |
P2385 Lilypad Pond |
普及 |
|
|
|
| 洛谷 |
P2895 流星雨 |
普及 |
|
|
|
| 洛谷 |
[[P2960 Invasion of the Milkweed]] |
普及 |
|
|
|
| 洛谷 |
[[P7995 Walking Home]] |
普及 |
|
|
|
| 洛谷 |
P8628 穿越雷区 |
普及 |
⭐⭐⭐ |
状态扩展 BFS(三维状态) |
用三维状态 dist[x][y][last] 表示到达 \((x,y)\) 且上一步经过的格子类型为 last(0 表示 +,1 表示 -)的最短距离;从起点 \(A\) 出发向四个方向初始化,若邻居是 + 则 dist[nx][ny][0]=1 入队,若是 - 则 dist[nx][ny][1]=1 入队;BFS 扩展时 need = 1-last 表示下一步需要的格子类型,只有当邻居是 need 对应的类型或为终点 \(B\) 时才可转移,更新 nlast 后入队;最终答案取 dist[edx][edy][0] 和 dist[edx][edy][1] 的最小值,若均不可达输出 -1;时间复杂度 \(O(n^2)\)。 |
| 洛谷 |
U203285 找到最大岛 |
普及 |
|
|
|
| 洛谷 |
AT_abc460_d Repeatedly Repainting |
普及+ |
⭐⭐⭐ |
多源 BFS(八方向) + 奇偶性判断 |
先预处理移除内部黑格(八邻域全为黑的格子永远不会变白),然后将所有剩余黑格作为多源起点同时入队 BFS,计算每个位置到最近黑格的最短距离 f[i][j];操作 \(10^{100}\) 次后,最终颜色只与距离奇偶性有关——f[i][j] % 2 == 0 输出 #(黑色),否则输出 .(白色);八方向扩展覆盖上下左右和四个对角线,时间复杂度 \(O(H \times W)\)。 |
| 洛谷 |
P1379 八数码难题 |
普及+ |
|
|
|
| 洛谷 |
[[P1849 Tractor]] |
普及+ |
|
|
|
| 洛谷 |
P2845 Switching on the Lights |
普及+ |
|
|
|
| 洛谷 |
[[P2919 Guarding the Farm]] |
普及+ |
|
|
|
| 洛谷 |
[[P3456 GRZ-Ridges and Valleys]] |
普及+ |
|
|
|
| 洛谷 |
[[P3663 Why Did the Cow Cross the Road III]] |
普及+ |
|
|
|
| 洛谷 |
P5195 Knights of Ni |
普及+ |
⭐⭐⭐ |
两次 BFS(多源 BFS) |
第一次 BFS 从起点出发,计算 dist1(不能经过骑士位置 3);第二次多源 BFS 从所有骑士位置同时出发,计算 dist2(可以经过任何可通行区域);遍历所有灌木位置 4,取 dist1[r][c] + dist2[r][c] 的最小值作为答案(即起点→灌木→骑士的最短路径);两次 BFS 时间复杂度均为 \(O(W \times H)\)。 |
| 洛谷 |
[[P10487 Nightmare II]] |
普及+ |
|
|
|
| 洛谷 |
[[P4667 Switch the Lamp On电路维修]] |
提高+ |
|
|
|
| AtCoder |
AT_awc0083_d Escape from the Ice Rink |
|
⭐⭐⭐ |
BFS + 滑动扩展(四方向) |
每次移动选择一个方向后一直滑动到边界或柱子前一个格子才停止,用 BFS 按层遍历求最少移动次数;对于每个出队节点,遍历上下左右四个方向,用 while 循环模拟滑动过程(nx += dx[i], ny += dy[i]),直到超出边界或 mp.count({nx,ny}) 遇到柱子,记录滑动终点;若终点未访问过则更新距离并入队;map<PII,int> 存储柱子位置实现 \(O(\log N)\) 查询,时间复杂度 \(O(H \times W \times (H+W))\),若终点不可达输出 `-1$。 |
| 学而思编程 |
重新涂色 |
普及进阶 |
⭐⭐⭐ |
BFS(最短路径) |
从起点 \((1,1)\) 进行 BFS 求到终点 \((H,W)\) 的最短路径长度 dist[H][W](步数),若无法到达则输出 \(-1\);若能到达,最高分数等于总白格数 cnt 减去最短路径上必须经过的格子数(dist[H][W] + 1,包含起点和终点),即把不在最短路径上的所有白格涂黑,时间复杂度 \(O(H \cdot W)\)(\(H,W \le 50\))。 |
【IDA*】
算法模板
算法应用
【A*】
算法模板
算法应用
【折半搜索】
算法模板
DFS版折半搜索
void dfs(..., int type) // type用于标记属于前半部分还是后半部分
{
// 标准dfs过程
if (...)
{
// 达到条件后的处理
if (type==1) l[sum]++; // 保存在前半部分
else r[sum]++; // 保存在后半部分
return;
}
dfs(..., type);
dfs(..., type);
}
unordered_map<int, int> l, r; // 用map记录前半部分和后半部分
dfs(1, mid, 0, 1); // 搜索前半部分
dfs(mid+1, n, 0, 2); // 搜索后半部分
枚举版折半搜索
// 枚举前半部分
for (i : 前半部分)
{
// 将结果存到map中
}
// 枚举后半部分
for (i : 后半部分)
{
// 得到后半部分结果
// 计算需要的前半部分的结果
// 累加组合数(求和的过程)
}
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| 洛谷 |
P4799 世界冰球锦标赛 |
普及+ |
⭐⭐⭐⭐ |
折半搜索(Meet in the Middle) + DFS + 二分查找 |
将 \(N\) 个数分成前后两半,分别用 DFS 枚举所有子集和存入 sa 和 sb(剪枝:若剩余最小值超过剩余预算则返回),对两个数组排序后,对于 sb 中每个元素 \(s\),用 upper_bound 在 sa 中查找满足 sa[i] <= M - s 的个数并累加,最后加上 sa.size() 和 sb.size()(单一半部分的方案)以及空集;将复杂度从 \(O(2^N)\) 降为 \(O(2^{N/2} \cdot N)\)。 |
| 洛谷 |
[[P10484 送礼物]] |
普及+ |
|
|
|
| AtCoder |
AT_awc0002_e Assortment of Sweets |
|
|
|
|
| AtCoder |
AT_awc0048_e Team Formation |
|
⭐⭐⭐⭐ |
折半搜索 + 哈希表计数 |
将 \(N\) 个学生分成前后两半,前半部分用位运算枚举所有子集,用 mp1[cnt][sum] 哈希表记录选择 cnt 人、技能值和模 \(M\) 为 sum 的方案数;后半部分同样枚举,对于每个子集计算 need_cnt = K - cnt(还需选择的人数)和 need_sum = (M - sum) % M(还需满足的余数),查询 mp1[need_cnt][need_sum] 累加答案;结果对 \(10^9+7\) 取模,将复杂度从 \(O(C(N,K))\) 降为 \(O(2^{N/2} \cdot N)\)。 |
【迭代加深】
算法模板
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| AcWing |
170 加成序列 |
|
|
|
|
| 洛谷 |
[[UVA529 Addition Chains]] |
提高+ |
|
|
|
【舞蹈链】
算法模板
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| 洛谷 |
[[P1784 数独]] |
普及 |
|
|
|
| 洛谷 |
[[P4929 舞蹈链(DLX)]] |
省选 |
|
|
|