最低多少钱可以注册公司漯河搜狗关键词优化排名软件
宽度优先搜索的过程中,每次都会从当前点向外扩展⼀层,所以会具有⼀个最短路的特性。因此,宽搜不仅能搜到所有的状态,⽽且还能找出起始状态距离某个状态的最⼩步数。
但是,前提条件是每次扩展的代价都是 1 ,或者都是相同的数。宽搜常常被⽤于解决边权为 1 的最短路问题。
一、BFS
1. ⻢的遍历
题⽬来源:洛⾕
题⽬链接:P1443 马的遍历 - 洛谷
难度系数:★★
(1)题目描述
(2)算法原理
题⽬要求到达某个点最少要⾛⼏步,因此可以⽤ bfs 解决。因为当权值为 1 时,bfs 每次都是扩展
距离起点等距离的⼀层,天然具有最短性。
那就从起点开始,⼀层⼀层的往外搜,⽤⼀个 dist 数组记录最短距离。
(3)参考代码
#include <iostream>
#include <queue>
#include <cstring>using namespace std;typedef pair<int, int> PII;const int N = 410;int n, m, x, y;
int dist[N][N];int dx[] = {1, 2, 2, 1, -1, -2, -2, -1};
int dy[] = {2, 1, -1, -2, -2, -1, 1, 2};void bfs()
{memset(dist, -1, sizeof dist);queue<PII> q;q.push({x, y});dist[x][y] = 0;while(q.size()){auto t = q.front(); q.pop();int i = t.first, j = t.second;for(int k = 0; k < 8; k++){int x = i + dx[k], y = j + dy[k];if(x < 1 || x > n || y < 1 || y > m) continue;if(dist[x][y] != -1) continue;dist[x][y] = dist[i][j] + 1; // 更细结果q.push({x, y});}}
}int main()
{cin >> n >> m >> x >> y;bfs();for(int i = 1; i <= n; i++){for(int j = 1; j <= m; j++){cout << dist[i][j] << " ";}cout << endl;}return 0;
}
2. kotori和迷宫
题⽬来源:⽜客⽹
题⽬链接:kotori和迷宫
难度系数:★★
(1)题目描述
(2)算法原理
经典 bfs 问题。
从迷宫的起点位置逐层开始搜索,每搜到⼀个点就标记⼀下最短距离。当把整个迷宫全部搜索完毕之 后,扫描整个标记数组,求出出⼝的数量以及最短的距离。
注意:走到 "e" 之后就不能继续遍历了
(3)参考代码
#include <iostream>
#include <queue>
#include <cstring>using namespace std;typedef pair<int, int> PII;const int N = 35;int n, m, x, y;
char a[N][N];
int dist[N][N];int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};void bfs()
{memset(dist, -1, sizeof dist);queue<PII> q;q.push({x, y});dist[x][y] = 0;while(q.size()){auto t = q.front(); q.pop();int i = t.first, j = t.second;for(int k = 0; k < 4; k++){int x = i + dx[k], y = j + dy[k];if(x >= 1 && x <= n && y >= 1 && y <= m && a[x][y] != '*' && dist[x][y] == -1){dist[x][y] = dist[i][j] + 1;if(a[x][y] == 'e') {continue;}q.push({x, y});}}}
}int main()
{cin >> n >> m;for(int i = 1; i <= n; i++){for(int j = 1; j <= m; j++){cin >> a[i][j];if(a[i][j] == 'k'){x = i; y = j;}}}bfs();// 统计结果int cnt = 0, ret = 1e9;for(int i = 1; i <= n; i++){for(int j = 1; j <= m; j++){if(a[i][j] == 'e' && dist[i][j] != -1){cnt++;ret = min(ret, dist[i][j]);}}}if(cnt == 0) cout << -1 << endl;else cout << cnt << " " << ret << endl;return 0;
}
3. CatchThatCow
题⽬来源:洛⾕
题⽬链接:P1588 [USACO07OPEN] Catch That Cow S - 洛谷
难度系数:★★
(1)题目描述
(2)算法原理
可以暴⼒枚举出所有的⾏⾛路径,因为是求最少步数,所以可以⽤ bfs 解决:
- 从起点位置开始搜索,每次向外扩展三种⾏⾛⽅式;
- 当第⼀次搜到⽜的位置时,就是最短距离。
如果不做任何处理,时间和空间都会超。因为我们会搜索到很多⽆效的位置,所以我们要加上剪枝策略:
- 当 −1 减到负数的时候,剪掉;
因为如果⾛到负数位置,还是需要回头⾛到正数位置,⼀定不是最优解。
- 当 +1 操作越过 y 的时候,剪掉;
如果 +1 之后⼤于 y ,说明本⾝就在 y 位置或者 y 的右侧,你再往右⾛还是需要再向左⾛回去。⼀定不是最优解,剪掉。
- 当 y 是偶数,并且当 ×2 操作之后⼤于 y 的时候,剪掉,
因为不如先减到 y 的⼀半然后再乘;设当前数是 x ,那么:
- 先乘后减,总的步数 t1 = 2x − y + 1 ;
- 先减后乘,总的步数 t2 = x − y/2 + 1 ;
- t1 − t2 = 2x − y + 1 − (x − y/2 + 1) = x − y/2 > 0 ;
- 因此,先乘后减不如先减后乘。
设 y 是奇数的时候,那么 y + 1 就是偶数,根据 3 可得,×2 操作不能超过y + 1 。
(3)参考代码
#include <iostream>
#include <queue>
#include <cstring>using namespace std;const int N = 1e5 + 10;int n = 1e5;
int x, y;
int dist[N];void bfs()
{queue<int> q;q.push(x);dist[x] = 0;while(q.size()){auto t = q.front(); q.pop();int a = t + 1, b = t - 1, c = t * 2;if(a <= n && dist[a] == -1){dist[a] = dist[t] + 1;q.push(a);}if(b > 0 && dist[b] == -1){dist[b] = dist[t] + 1;q.push(b); }if(c <= n && dist[c] == -1){dist[c] = dist[t] + 1;q.push(c);}// 剪枝if(a == y || b == y || c == y) return;}
}int main()
{int T; cin >> T;while(T--){// 注意清空数据memset(dist, -1, sizeof dist);cin >> x >> y;bfs();cout << dist[y] << endl;}return 0;
}
4. ⼋数码难题
题⽬来源:洛⾕
题⽬链接:P1379 八数码难题 - 洛谷
难度系数:★★★
(1)题目描述
(2)算法原理
经过之前那么多题的铺垫,这道题的解法还是容易想到的。因为要求的是最短步数,因此可以⽤bfs 解决。
- 从起始状态开始,每次扩展上下左右交换后的状态;
- 在搜索的过程中,第⼀次遇到最终状态就返回最短步数。
算法原理虽然容易,但是实现起来⽐较⿇烦,我们要想办法处理下⾯⼏件事情:
- 如何记录⼀个3 × 3 的棋盘?
可以⽤字符串。从上往下,从左往右将棋盘内的数依次存到⼀个字符串⾥,来标记棋盘的状态。
- 如何记录最短路?
可以⽤ unordered_map < string, int > 来标记最短距离;
- 如何通过⼀个字符串找到交换之后的字符串?
策略⼀:先把字符串还原成⼆维矩阵,然后交换 0 成字符串。与四周的数字,最后再把交换之后的棋盘还原成字符串。
虽然可⾏,但是太过于⿇烦。我们其实可以通过计算,快速得出⼆维坐标与⼀维下标相互转换前后的值。如下图:
这个技巧特别常⽤,我们可以推⼴到 n × m 的矩阵坐标 (x, y) ,映射成⼀个数 pos ,可以起到空间优化的效果。后续做题中我们就会遇到。
因此,我们可以直接在字符串中,找出交换前后的下标,直接交换字符串对应位置,就能得到交换之后的状态。
(3)参考代码
#include <iostream>
#include <unordered_map>
#include <queue>using namespace std;string s;
string aim = "123804765";
unordered_map<string, int> dist;int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};void bfs()
{queue<string> q;q.push(s);dist[s] = 0;while(q.size()){string t = q.front(); q.pop();int pos = 0;while(t[pos] != '0') pos++;int x = pos / 3, y = pos % 3; // 计算二维矩阵中对应的位置for(int i = 0; i < 4; i++){int a = x + dx[i], b = y + dy[i];if(a >= 0 && a <= 2 && b >= 0 && b <= 2){string next = t;// (x, y) 与 (a, b) 做交换int p = 3 * a + b;swap(next[p], next[pos]);if(dist.count(next)) continue;dist[next] = dist[t] + 1;q.push(next);if(next == aim) return; // 剪枝}}}
}int main()
{cin >> s;bfs();cout << dist[aim] << endl;return 0;
}
二、多源BFS
- 单源最短路问题 vs 多源最短路问题
- 当问题中只存在⼀个起点时,这时的最短路问题就是单源最短路问题。
- 当问题中存在多个起点⽽不是单⼀起点时,这时的最短路问题就是多源最短路问题。
- 多源 BFS
多源最短路问题的边权都为 1 时,此时就可以⽤多源 BFS 来解决。
- 解决⽅式
把这些源点汇聚在⼀起,当成⼀个"超级源点"。然后从这个"超级源点"开始,处理最短路问题。
落实到代码上时:
- 初始化的时候,把所有的源点都加⼊到队列⾥⾯;
- 然后正常执⾏ bfs 的逻辑即可。
也就是初始化的时候,⽐普通的 bfs 多加⼊⼏个起点。
1. 矩阵距离
题⽬来源:⽜客⽹
题⽬链接:矩阵距离
难度系数:★★
(1)题目描述
(2)算法原理
正难则反:
- 如果针对某⼀个点,直接去找最近的 1 ,我们需要对所有的 0 都来⼀次 bfs,这个时间复杂度是 接受不了的。
- 但是我们如果反着来想,从 1 开始向外扩展,每遍历到⼀个 0 就更新⼀下最短距离。这样仅需⼀ 次 bfs,就可以把所有点距离 1 的最短距离更新出来。
由于 1 的数量很多,因此可以把所有的 1 看成⼀个超级源点,从这个超级源点开始⼀层⼀层的向外 扩展。实现起来也很简单,就是在初始化阶段把所有 1 的坐标加⼊到队列中,然后正常 bfs 。
(3)参考代码
#include <iostream>
#include <queue>
#include <cstring>using namespace std;typedef pair<int, int> PII;const int N = 1010;int n, m;
char a[N][N];
int dist[N][N];int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};void bfs()
{memset(dist, -1, sizeof dist);queue<PII> q;// 1. 所有的起点加入到队列里面for(int i = 1; i <= n; i++)for(int j = 1; j <= m; j++)if(a[i][j] == '1'){q.push({i, j});dist[i][j] = 0;}// 2. 正常的 bfswhile(q.size()){auto t = q.front(); q.pop();int x = t.first, y = t.second;for(int i = 0; i < 4; i++){int a = x + dx[i], b = y + dy[i];if(a >= 1 && a <= n && b >= 1 && b <= m && dist[a][b] == -1){dist[a][b] = dist[x][y] + 1;q.push({a, b});}}}
}int main()
{cin >> n >> m;for(int i = 1; i <= n; i++)for(int j = 1; j <= m; j++)cin >> a[i][j];bfs();for(int i = 1; i <= n; i++){for(int j = 1; j <= m; j++){cout << dist[i][j] << " ";}cout << endl;}return 0;
}
2. 刺杀⼤使
题⽬来源:洛⾕
题⽬链接:P1902 刺杀大使 - 洛谷
难度系数:★★★
(1)题目描述
(2)算法原理
直接找答案显然是不现实的,因为能⾛的路径实在是太多了,如果全都枚举出来时间上吃不消。但是题⽬要求的是最⼤值最⼩化,可以尝试⽤⼆分来优化枚举。
设最终结果是 x ,会发现⼀个性质:
- 当规定搜索过程中的最⼤值⼤于等于 x 时,我们⼀定可以从第⼀⾏⾛到最后⼀⾏;
- 当规定搜索过程中的最⼤值⼩于 x 时,我们⼀定不能⾛到最后⼀⾏。
因此,我们可以⼆分最终结果,通过 bfs 或者 dfs 来判断是否能⾛到最后⼀⾏。
如果⽤ dfs ,那就从第⼀⾏的每⼀列开始,全都搜索⼀遍。如果⽤ bfs ,可以看成多源 bfs 问题,直接把所有的源点加⼊队列中,然后正常搜索即可。
(3)参考代码
#include <iostream>
#include <queue>
#include <cstring>using namespace std;typedef pair<int, int> PII;const int N = 1010;int n, m;
int p[N][N];
bool st[N][N]; // 在宽搜的过程中,不走重复路int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};// 伤害不超过 mid 的情况下,能够到达第 n 行
bool bfs(int mid)
{if(n == 1) return true;memset(st, 0, sizeof st);queue<PII> q;// 1. 把所有的源点加入到队列里面for(int j = 1; j <= m; j++){q.push({1, j});st[1][j] = true;}while(q.size()){auto t = q.front(); q.pop();int x = t.first, y = t.second;for(int i = 0; i < 4; i++){int a = x + dx[i], b = y + dy[i];if(a >= 1 && a <= n && b >= 1 && b <= m && p[a][b] <= mid && st[a][b] == false){st[a][b] = true;q.push({a, b});if(a == n) return true;}}}return false;
}int main()
{cin >> n >> m;int l = 0, r = 0;for(int i = 1; i <= n; i++){for(int j = 1; j <= m; j++){cin >> p[i][j];r = max(r, p[i][j]);}}// 二分答案while(l < r){int mid = (l + r) / 2;if(bfs(mid)) r = mid;else l = mid + 1;}cout << l << endl;return 0;
}
三、01 BFS
01 BFS ⼜称 双端队列 BFS。
在最短路问题中,边权值可能为 1 也可能为 0。那么,在 BFS 的过程中,可以将边权为 0 扩展出来的点放到队⾸,边权为 1 扩展出来的点放到队尾。这样就能保证像普通 BFS ⼀样整个队列队⾸到队尾权值单调不下降。
注意:
不同于普通的 bfs 问题,01bfs 问题在遍历的过程中,如果遇到之前已经遍历过的结点,有可能会找到⼀条更优的路线。在写代码的时候,要注意判断这⼀点。
1. ⼩明的游戏
题⽬来源:洛⾕
题⽬链接:P4554 小明的游戏 - 洛谷
难度系数:★★
(1)题目描述
(2)算法原理
01bfs 模板题。
- 如果⾛到相同格⼦,权值为 0 ,更新最短距离,然后加⼊到队头;
- 如果⾛到不同格⼦,权值为 0 ,更新最短距离,然后加⼊到队尾。
其余的搜索⽅式与常规 bfs ⼀模⼀样,但是注意松弛操作。
注意:
输入时要从 0 开始,不能从 1 开始,因为给的坐标中可能有 0 ;
(3)参考代码
#include <iostream>
#include <deque>
#include <cstring>using namespace std;typedef pair<int, int> PII;const int N = 510;int n, m, x1, y1, x2, y2;
char a[N][N];
int dist[N][N];int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};void bfs()
{if(x1 == x2 && y1 == y2){dist[x2][y2] = 0;return;}memset(dist, -1, sizeof dist);deque<PII> q;q.push_back({x1, y1});dist[x1][y1] = 0;while(q.size()){auto t = q.front(); q.pop_front();int i = t.first, j = t.second;if(i == x2 && j == y2) return; // 剪枝for(int k = 0; k < 4; k++){int x = i + dx[k], y = j + dy[k];if(x >= 0 && x < n && y >= 0 && y < m){char cur = a[i][j], next = a[x][y];int w = (cur == next ? 0 : 1);if(dist[x][y] == -1){dist[x][y] = dist[i][j] + w;// 01 BFSif(w == 0) q.push_front({x, y});else q.push_back({x, y});}else if(dist[i][j] + w < dist[x][y]) // 虽然是第二次遇到,但是这种情况更优{// 松弛操作dist[x][y] = dist[i][j] + w;}// if(x == x2 && y == y2) return;}}}
}int main()
{while(cin >> n >> m, n && m){for(int i = 0; i < n; i++)for(int j = 0; j < m; j++)cin >> a[i][j];cin >> x1 >> y1 >> x2 >> y2;bfs();cout << dist[x2][y2] << endl;}return 0;
}
2. ThreeStates
题⽬来源:洛⾕
题⽬链接:CF590C Three States - 洛谷
难度系数:★★★★
(1)题目描述
(2)算法原理
正难则反:
- 直接找出结果点是很⿇烦的,需要枚举所有的点,然后每个点都要来⼀次 bfs ,这样是会超时的。
- 可以依次从三个国家出发,⽤ bfs 计算出来到达所有点的最短距离。求出所有距离之后,重新遍历所有的点,分情况求出到三个国家的最短距离。
算法原理很简单,但是⾥⾯有很多细节需要注意:
- 因为每个国家可能有很多点,并且国家的点与点之间不连通,因此我们要⽤ 多源bfs 求某个国家到所有点的最短距离;
- 国家与国家之间的点如果相连,它们之间的距离是 0 (⽆论是不是同⼀个国家,在我们这道题⾥⾯,它们的距离都是 0 ,因为都不需要修路)。那么我们这道题⾥⾯边的权值要么是0 ,要么是1 。因此需要⽤ 01bfs 来更新所有的距离;
- 计算某⼀个点到三个国家的最短距离时,应该分情况讨论。设 a, b, c 分别表⽰三个国家到该点的最短距离:
- 如果该点是⼀个国家,最短距离为 a + b + c ;
- 如果该点是⼀个荒地,最短距离为 a +b+c−2 ,因为我们计算最短距离的时候,荒地会被计算三次,所以要减去两次。

(3)参考代码
#include <iostream>
#include <deque>
#include <cstring>using namespace std;typedef pair<int, int> PII;const int N = 1010;int n, m;
char a[N][N];
int dist[4][N][N];
// dist[1] dist[2] dist[3]int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};void bfs(int num)
{memset(dist[num], -1, sizeof dist[num]);// 多源bfsdeque<PII> q;for(int i = 1; i <= n; i++)for(int j = 1; j <= m; j++){if(a[i][j] - '0' == num){q.push_back({i, j});dist[num][i][j] = 0;}}// 01bfswhile(q.size()){auto t = q.front(); q.pop_front();int i = t.first, j = t.second;for(int k = 0; k < 4; k++){int x = i + dx[k], y = j + dy[k];if(x >= 1 && x <= n && y >= 1 && y <= m && a[x][y] != '#'){int w = (a[x][y] == '.' ? 1 : 0);if(dist[num][x][y] == -1) // 第一次遇到{dist[num][x][y] = dist[num][i][j] + w;if(w) q.push_back({x, y});else q.push_front({x, y});}else if(dist[num][i][j] + w < dist[num][x][y]){dist[num][x][y] = dist[num][i][j] + w;}}}}
}int main()
{cin >> n >> m;for(int i = 1; i <= n; i++)for(int j = 1; j <= m; j++)cin >> a[i][j];bfs(1); bfs(2); bfs(3);int ret = 0x3f3f3f3f;for(int i = 1; i <= n; i++)for(int j = 1; j <= m; j++){if(a[i][j] == '#') continue;int x = dist[1][i][j], y = dist[2][i][j], z = dist[3][i][j];if(x == -1 || y == -1 || z == -1) continue;if(a[i][j] == '.') ret = min(ret, x + y + z - 2);else ret = min(ret, x + y + z);}if(ret == 0x3f3f3f3f) cout << -1 << endl;else cout << ret << endl;return 0;
}
四、Floodfill 问题
Floodfill 算法,⼜称漫⽔填充,像素法,本质是在寻找具有相同性质的联通块。
1. LakeCounting
题⽬来源:洛⾕
题⽬链接:P1596 [USACO10OCT] Lake Counting S - 洛谷
难度系数:★
(1)题目描述
(2)算法原理
遍历整个矩阵,当遇到⼀个没有标记过的⽔坑时:
- 计数;
- 然后⽤ bfs 或者 dfs 将整个湖全都标记⼀下。
整个矩阵遍历⼀遍,就能得出湖的数量。
(3)参考代码
#include <iostream>
#include <queue>
#include <cstring>using namespace std;const int N = 110;int n, m;
char a[N][N];
bool st[N][N]; // 标记哪些区域已经被搜索过了int dx[] = {0, 0, 1, -1, 1, 1, -1, -1};
int dy[] = {1, -1, 0, 0, 1, -1, 1, -1};void dfs(int i, int j)
{st[i][j] = true;for(int k = 0; k < 8; k++){int x = i + dx[k], y = j + dy[k];if(x >= 1 && x <= n && y >= 1 && y <= m && a[x][y] == 'W' && st[x][y] == false){dfs(x, y);}}
}void bfs(int i, int j)
{queue<pair<int, int>> q;q.push({i, j});st[i][j] = true;while(q.size()){auto t = q.front(); q.pop();int i = t.first, j = t.second;for(int k = 0; k < 8; k++){int x = i + dx[k], y = j + dy[k];if(x >= 1 && x <= n && y >= 1 && y <= m && a[x][y] == 'W' && st[x][y] == false){st[x][y] = true;q.push({x, y});}}}
}int main()
{cin >> n >> m;for(int i = 1; i <= n; i++)for(int j = 1; j <= m; j++)cin >> a[i][j];int ret = 0;for(int i = 1; i <= n; i++){for(int j = 1; j <= m; j++){if(a[i][j] == 'W' && st[i][j] == false){ret++;// dfs(i, j); // 把这块区域打上标记bfs(i, j); // 把这块区域打上标记}}}cout << ret << endl;return 0;
}
2. 填涂颜⾊
题⽬来源:洛⾕
题⽬链接:P1162 填涂颜色 - 洛谷
难度系数:★★
(1)题目描述
(2)算法原理
正难则反:
直接找出被 1包围的 0 是很困难的,因为很难确定当前搜索到的这个 0 是否是被包围的。但是,我们如果从边缘的 0开始搜索,搜索到的 0 ⼀定是没有被包围的。
因此,我们可以从边缘的 0 开始搜索,标记所有与边缘 0 相连的联通块。那么,没有被标记过的 0 就是被包围的。
⼩技巧:
- 我们可以把整个矩阵的外围包上⼀层 0 ,这样只⽤从 [0, 0] 位置开始搜即可。不⽤遍历第⼀⾏第⼀列,最后⼀⾏以及最后⼀列
(3)参考代码
#include <iostream>
#include <cstring>using namespace std;const int N = 35;int n;
int a[N][N];
bool st[N][N]; // 只会标记边缘的 0int dx[] = {0, 0, -1, 1};
int dy[] = {1, -1, 0, 0};void dfs(int i, int j)
{st[i][j] = true;for(int k = 0; k < 4; k++){int x = i + dx[k], y = j + dy[k];// 注意此时的边界if(x >= 0 && x <= n + 1 && y >= 0 && y <= n + 1 && a[x][y] == 0 && st[x][y] == 0){dfs(x, y);}}
}int main()
{while(cin >> n){for(int i = 1; i <= n; i++)for(int j = 1; j <= n; j++)cin >> a[i][j];dfs(0, 0);for(int i = 1; i <= n; i++){for(int j = 1; j <= n; j++){if(a[i][j]) cout << a[i][j] << " ";else if(st[i][j]) cout << 0 << " ";else cout << 2 << " ";}cout << endl;}}return 0;
}