广度优先搜索直通车
前言
广度优先搜索(Breadth First Search, BFS),即在搜索树内搜索,一起处理相同层级的状态,再去处理下一级的状态。由于不是一个劲地往前搜,因此处理可行性问题是搜到结果就可以直接退出,但是深度优先搜索最坏得遍历完整个搜索树才能退出。广度优先搜索也不是没有坏处,它会破坏先驱状态,因此在路径问题中你需要整个路径都加到状态内。
广度优先搜索一般使用队列实现,根据不同的情况可以使用双端队列、优先队列,具体请看 STL 章节。
例题
普通 BFS
这类 bfs 在每次转移时只会加上一个相同的值,因此最先遇到的答案必然是最优的,搜的时间越久,状态就越劣。
机器人移动学会(RMI)现在正尝试用机器人搬运物品。机器人的形状是一个直径 \(1.6\) 米的球。在试验阶段,机器人被用于在一个储藏室中搬运货物。储藏室是一个 \(N\times M\) 的网格,有些格子为不可移动的障碍。机器人的中心总是在格点上,当然,机器人必须在最短的时间内把物品搬运到指定的地方。机器人接受的指令有:
- 向前移动 \(1\) 步
Creep - 向前移动 \(2\) 步
Walk - 向前移动 \(3\) 步
Run - 向左转(
Left) - 向右转(
Right)
每个指令所需要的时间为 \(1\) 秒。请你计算一下机器人完成任务所需的最少时间。
输入格式
第一行为两个正整数 \(N,M\ (1\le N,M\le50)\),下面 \(N\) 行是储藏室的构造,\(0\) 表示无障碍,\(1\) 表示有障碍,数字之间用一个空格隔开。接着一行有 \(4\) 个整数和 \(1\) 个大写字母,分别为起始点和目标点左上角网格的行与列,起始时的面对方向(东 \(\tt E\),南 \(\tt S\),西 \(\tt W\),北 \(\tt N\)),数与数,数与字母之间均用一个空格隔开。终点的面向方向是任意的。
输出格式
一个整数,表示机器人完成任务所需的最少时间。如果无法到达,输出 \(-1\)。
解法
我们可以使用一个队列,定义结构体存储状态,然后每次从队列里面取出状态转移,并把步数加一。如果找到了答案,那就立马记录并退出。
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int kMaxN = 55;
const int kD[5][2] = {{114514, 114514}, {0, 1}, {1, 0}, {0, -1}, {-1, 0}};
struct Node { // 结构体
int x, y, s, to; // 坐标、步数和方向
};
int f[kMaxN][kMaxN][5], a[kMaxN][kMaxN];
iny n, m, stx, sty, edx, edy, to, ans = 1e9;
char c;
queue<Node> q; // 队列
// 记录状态函数
void Record(int x, int y, int s, int to) {
if (x < 1 || x >= n || y < 1 || y >= m || a[x][y] || s >= f[x][y][to]) {
return;
}
// 如果超出了地图的界限或者已经有更优的状态了,那就剪枝
f[x][y][to] = s, q.push({x, y, s, to});
// 记录下当前最优值并进入队列
}
void bfs() { // 搜索
for (Record(stx, sty, 0, to); q.size(); q.pop()) {
Node t = q.front();
if (t.x == edx && t.y == edy) { // 如果满足结束条件
ans = t.s; // 记录答案
return; // 退出
}
Record(t.x + kD[t.to][0], t.y + kD[t.to][1], t.s + 1, t.to); // 走一步
if (!a[t.x + kD[t.to][0]][t.y + kD[t.to][1]]) {
Record(t.x + 2 * kD[t.to][0], t.y + 2 * kD[t.to][1], t.s + 1, t.to);
} // 走两步
if (!a[t.x + kD[t.to][0]][t.y + kD[t.to][1]] &&
!a[t.x + 2 * kD[t.to][0]][t.y + 2 * kD[t.to][1]]) {
Record(t.x + 3 * kD[t.to][0], t.y + 3 * kD[t.to][1], t.s + 1, t.to);
} // 走三步,注意这里三个格子 都要判断
Record(t.x, t.y, t.s + 1, (t.to == 1 ? 4 : t.to - 1));
// 左转
Record(t.x, t.y, t.s + 1, (t.to == 4 ? 1 : t.to + 1));
// 右转
}
}
int main() {
fill(f[0][0], f[0][0] + kMaxN * kMaxN * 5, 1e9); // 记得初始化
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
for (int j = 1, x; j <= m; ++j) {
cin >> x;
x && (a[i - 1][j - 1] = a[i - 1][j] = a[i][j - 1] = a[i][j] = 1);
}
}
cin >> stx >> sty >> edx >> edy >> c;
to = (c == 'E' ? 1 : (c == 'S' ? 2 : (c == 'W' ? 3 : 4)));
// 起始方向
bfs();
cout << (ans == 1e9 ? -1 : ans) << '\n';
return 0;
}
优先队列 BFS
给定一张 \(N\) 个点(编号为 \(1 \sim N\)),\(M\) 条边的无向图,保证无重边无自环。现在有 \(K\) 个被标记的点,其中第 \(i\) 个被标记的点的编号为 \(p_i\),任何从 \(p_i\) 出发经过不超过 \(h_i\) 条边能到达的点都会被染色(包括 \(p_i\) 自身)。你需要求出这张图最终有哪些点被染色。
输入格式
第一行三个正整数 \(N,M,K\),含义见题目描述。接下来 \(M\) 行,每行两个正整数 \(a_i,b_i\),表示编号为 \(a_i,b_i\) 的点连有一条无向边。接下来 \(K\) 行,每行两个正整数 \(p_i,h_i\),含义见题目描述。
输出格式
第一行一个数字 \(G\),表示被染色的点的个数。第二行 \(G\) 个数字,表示被染色的点,按照从小到大的顺序输出。
数据范围
\(1 \le N \le 2 \times 10^5\),\(0 \le M \le 2 \times 10^5\),\(1 \le K,a_i,b_i,p_i,h_i \le N\),\(p_i\) 互不相同。保证给定的图无重边,无自环。
解法
首先这道题一看就是图上的 bfs,具体的图论知识可以看数据结构板块。但是由于状态转移合并并不是单纯相加,因此我们需要一个优先队列,每次取得都是最优值,因此结果也一定是最优值。
// 本题为著名教师 胖头鱼 的例题:s**t污染
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int kMaxN = 2e5 + 1;
struct Node { // 结点
int h, p; // 当前点位,距离
// 按照距离排序
friend bool operator<(const Node& a, const Node& b) {
return a.p < b.p;
// 按照剩余距离最大的排序
// 但是由于优先队列的比较是反着的,因此得写小于号
}
};
int f[kMaxN], v[kMaxN], n, m, k, x, y, ans;
vector<int> e[kMaxN];
priority_queue<Node> q; // 优先队列
void Record(Node t) {
if (f[t.h] - 1 < 0 || v[t.h]) {
// 如果已经访问过了节点或者无法到达那里
return; // 剪枝
}
v[t.h] = 1; // 打上标记
for (int i : e[t.h]) { // 枚举邻居
if (f[i] < f[t.h] - 1) { // 如果剩余距离更大,即更优
f[i] = f[t.h] - 1; // 更改
q.push({ i, f[i] }); // 加入队列
}
}
}
void bfs() {
for (; q.size(); q.pop()) { // 不停搜索
Record(q.top()); // 记录
}
}
int main() {
cin >> n >> m >> k;
fill(f + 1, f + n + 1, -1e9); // 初始化距离数组
for (int i = 1; i <= m; ++i) {
cin >> x >> y;
e[x].push_back(y), e[y].push_back(x);
// 邻接表加边
}
for (int i = 1; i <= k; ++i) {
cin >> x >> y; // 输入进去的答辩污染
q.push({ x, y });
f[x] = y;
}
bfs(); // 搜索
for (int i = 1; i <= n; ++i) {
ans += f[i] != -1e9; // 加上值
}
cout << ans << '\n';
for (int i = 1; i <= n; ++i) {
f[i] != -1e9 && (cout << i << ' ');
}
// 输出
return 0;
}

浙公网安备 33010602011771号