广度优先搜索直通车

前言

广度优先搜索(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;
}
posted @ 2023-10-15 16:29  haokee  阅读(23)  评论(0)    收藏  举报