BFS 应用
一、 BFS VS. DFS \text{BFS VS. DFS} BFS VS. DFS
BFS
\text{BFS}
BFS 适用于求源点和目标结点距离近的情况,例如求最少步数;
DFS
\text{DFS}
DFS 适用于求解一个任意符合方案中的一种或者遍历所有情况,例如能否到达、求所有路径数、全排列。
二、例题
1. 小蝌蚪找妈妈
1.1 审题
题目描述
小蝌蚪再次与青蛙妈妈失散。按照约定,青蛙妈妈会位于荷塘上的一片孤立的方形荷叶上,小蝌蚪只有完成两个步骤才有可能找到妈妈。你能帮帮小蝌蚪吗?第一个步骤是检查所有荷叶的形状:荷塘可视为一个 n × n n \times n n×n 的方阵,荷塘上有若干方形荷叶,用
"@"
表示,水域用"."
表示,青蛙妈妈只会站在孤立的方形荷叶上。在荷塘中,如果两个"@"
在上下或左右相邻的位置上,但分属两片不同的方形荷叶,则这两片方形荷叶相互接触了。如果存在相互接触的方形荷叶,请你告诉小蝌蚪"Contact."
。第二个步骤是统计荷塘中互不接触的方形荷叶的数量,以便找到妈妈的确切位置。如果荷塘里只存在互不接触的荷叶就输出"There are x leaves."
,其中 x x x 表示互不接触的荷叶数量。
输入描述
第一行为一个整数 n n n( 1 ≤ n ≤ 1000 1 \le n \le 1000 1≤n≤1000),表示荷塘的大小。接下来 n n n 行,每行 n n n 个字符,为
@
或.
。数据保证荷塘里一定有荷叶。
输出描述
一行一个字符串,如果荷塘里只存在互不接触的荷叶就输出
There are x leaves.
, x x x 表示互不接触的荷叶数量。否则输出Contact.
。
样例1
输入
4 @@.@ @@.@ ...@ @@@@
输出
Contact.
1.2 思路
典型的泛洪问题。
要先判断荷叶是否相连,其思路即:只要有拐角的地方,那就是相连。总共只有四种情况: [ @ . @ @ ] \begin{bmatrix}@ & . \\@ & @ \\\end{bmatrix} [@@.@] [ @ @ . @ ] \begin{bmatrix}@ & @ \\. & @ \\\end{bmatrix} [@.@@] [ . @ @ @ ] \begin{bmatrix}. & @ \\@ & @ \\\end{bmatrix} [.@@@] [ @ @ @ . ] \begin{bmatrix}@ & @ \\@ & . \\\end{bmatrix} [@@@.]。
1.3 参考答案
#include <iostream>
#include <queue>
using namespace std;
struct Node
{
int x, y;
};
int n, ans;
char a[1005][1005];
int dx[5] = {-1, 0, 0, 1};
int dy[5] = {0, -1, 1, 0};
queue <Node> q;
bool contact(int i, int j)
{
int cnt = 0;
if (a[i][j] == '@') cnt++;
if (a[i][j+1] == '@') cnt++;
if (a[i+1][j] == '@') cnt++;
if (a[i+1][j+1] == '@') cnt++;
return (cnt == 3);
}
void bfs(int x, int y)
{
a[x][y] = '.';
q.push({x, y});
while (!q.empty())
{
auto [fx, fy] = q.front();
q.pop();
for (int i = 0; i <= 3; i++)
{
int tx = fx+dx[i];
int ty = fy+dy[i];
if (tx>=1 && tx<=n && ty>=1 && ty<=n && a[tx][ty]=='@')
{
a[tx][ty] = '.';
q.push({tx, ty});
}
}
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> a[i][j];
for (int i = 1; i < n; i++)
for (int j = 1; j < n; j++)
if (contact(i, j))
{
cout << "Contact.";
return 0;
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (a[i][j] == '@')
{
ans++;
bfs(i, j);
}
cout << "There are " << ans << " leaves.";
return 0;
}
2. 流星雨
2.1 审题
题目描述
贝茜听说一场特别的流星雨即将到来:这些流星会撞向地球,并摧毁它们所撞击的任何东西。她为自己的安全感到焦虑,发誓要找到一个安全的地方(一个永远不会被流星摧毁的地方)。
如果将牧场放入一个直角坐标系中,贝茜现在的位置是原点,并且,贝茜不能踏上一块被流星砸过的土地。
根据预报,一共有 M M M 颗流星会坠落在农场上,其中第 i i i 颗流星会在时刻 T i T_i Ti 砸在坐标为 ( X i , Y i ) (X_i, Y_i) (Xi,Yi) 的格子里。流星的力量会将它所在的格子,以及周围 4 4 4 个相邻的格子都化为焦土,当然贝茜也无法再在这些格子上行走。
贝茜在时刻 0 0 0 开始行动,它只能在第一象限中,平行于坐标轴行动,每 1 1 1 个时刻中,她能移动到相邻的(一般是 4 4 4 个)格子中的任意一个,当然目标格子要没有被烧焦才行。如果一个格子在时刻 t t t 被流星撞击或烧焦,那么贝茜只能在 t t t 之前的时刻在这个格子里出现。 贝茜一开始在 ( 0 , 0 ) (0, 0) (0,0)。
请你计算一下,贝茜最少需要多少时间才能到达一个安全的格子。如果不可能到达(天要亡我贝茜乎!)输出−1
。
输入描述
输入文件名
meteor.in
。
共 M + 1 M + 1 M+1 行,第 1 1 1 行输入一个整数 M M M,接下来的 M M M 行每行输入三个整数分别为 X i , Y i , T i X_i, Y_i, T_i Xi,Yi,Ti。
输出描述
输出文件名
meteor.out
。
贝茜到达安全地点所需的最短时间,如果不可能,则为-1
。
样例1
输入
4 0 0 2 2 1 2 1 1 2 0 3 5
输出
5
提示
1 ≤ M ≤ 50000 1 \le M \le 50000 1≤M≤50000
0 ≤ T i ≤ 1000 0 \le T_i \le 1000 0≤Ti≤1000
0 ≤ X i , Y i , T i ≤ 300 0 \le X_i,Y_i,T_i \le 300 0≤Xi,Yi,Ti≤300
2.2 参考答案
#include <iostream>
#include <queue>
#include <cstdio>
#include <cstring>
#define e 0x3f3f3f3f
using namespace std;
struct Node
{
int x, y;
int step;
};
int n;
int x, y, t;
int a[305][305];
queue <Node> q;
bool vis[305][305];
int dx[5] = {-1, 0, 0, 1};
int dy[5] = {0, -1, 1, 0};
void bfs()
{
vis[0][0] = 1;
q.push({0, 0, 0});
while (!q.empty())
{
auto [fx, fy, fstep] = q.front();
q.pop();
if (a[fx][fy] == e)
{
cout << fstep;
return;
}
for (int i = 0; i <= 3; i++)
{
int tx = fx+dx[i];
int ty = fy+dy[i];
if (tx>=0 && ty>=0 && vis[tx][ty]==0 && a[tx][ty]>fstep+1)
{
vis[tx][ty] = 1;
q.push({tx, ty, fstep+1});
}
}
}
cout << -1;
return;
}
int main()
{
freopen("meteor.in", "r", stdin);
freopen("meteor.out", "w", stdout);
cin >> n;
memset(a, e, sizeof(a));
while (n--)
{
cin >> x >> y >> t;
// 标记爆炸时间
a[x][y] = min(a[x][y], t);
for (int i = 0; i <= 3; i++)
{
int tx = x+dx[i];
int ty = y+dy[i];
if (tx>=0 && ty>=0)
a[tx][ty] = min(a[tx][ty], t);
}
}
bfs();
fclose(stdin);
fclose(stdout);
return 0;
}
3. 迷宫游戏
3.1 审题
题目描述
小明在玩一款迷宫游戏,在游戏中他要控制自己的角色离开一间由 N × N N \times N N×N 个格子组成的二维迷宫。
小明的起始位置在左上角,他需要到达右下角的格子才能离开迷宫。
每一步,他可以移动到上下左右相邻的格子中(前提是目标格子可以经过)。
迷宫中有些格子小明可以经过,我们用'.'
表示;
有些格子是墙壁,小明不能经过,我们用'#'
表示。
此外,有些格子上有陷阱,我们用'X'
表示。除非小明处于无敌状态,否则不能经过。
有些格子上有无敌道具,我们用'%'
表示。
当小明第一次到达该格子时,自动获得无敌状态,无敌状态会持续 K K K 步,无敌时间不能累加。
之后如果再次到达该格子不会获得无敌状态了。
处于无敌状态时,可以经过有陷阱的格子,但是不会拆除 / 毁坏陷阱,即陷阱仍会阻止没有无敌状态的角色经过。
给定迷宫,请你计算小明最少经过几步可以离开迷宫。
输入描述
第一行包含两个整数 N N N 和 K K K。( 1 ≤ N ≤ 1000 1 \le N \le 1000 1≤N≤1000, 1 ≤ K ≤ 10 1 \le K \le 10 1≤K≤10)。
以下 N N N 行包含一个 N × N N \times N N×N 的矩阵。
矩阵保证左上角和右下角是'.'
。
输出描述
一个整数表示答案。如果小明不能离开迷宫,输出
-1
。
样例1
输入
5 3 ...XX ##%#. ...#. .###. .....
输出
10
3.2 思路
首先,Node
的参数就应该改为:
x
,
y
,
step
,
state
x,y,\text{step},\text{state}
x,y,step,state,分别表示
x
x
x 坐标、
y
y
y 坐标、当前步数、剩余的无敌状态时间。
然后,我们来分析一下 bfs()
如何修改:
- 边界以外的排除(
#
、+
) - 当没有标记的时候
·
fiv>0
(fiv
是队首的剩余无敌时间){tx, ty, fstep+1, fiv-1}
入队
fiv=0
{tx, ty, fstep+1, 0}
入队
X
fiv>0
{tx, ty, fstep+1, fiv-1}
入队
fiv<=0
- 不能走
%
{tx, ty, fstep+1, k}
入队
2.3 参考答案
#include <iostream>
#include <queue>
using namespace std;
struct Node
{
int x, y;
int step;
int iv;
};
int n, k;
char a[1005][1005];
bool vis[1005][1005];
int dx[5] = {-1, 0, 0, 1};
int dy[5] = {0, -1, 1, 0};
queue <Node> q;
void bfs()
{
vis[1][1] = 1;
q.push({1, 1, 0, 0});
while (!q.empty())
{
auto [fx, fy, fstep, fiv] = q.front();
q.pop();
if (fx==n && fy==n)
{
cout << fstep;
return;
}
for (int i = 0; i <= 3; i++)
{
int tx = fx+dx[i];
int ty = fy+dy[i];
if (tx<1 || tx>n || ty<1 || ty>n || a[tx][ty]=='#')
continue;
if (vis[tx][ty] == 0)
{
vis[tx][ty] = 1;
if (a[tx][ty] == '.')
{
if (fiv > 0) q.push({tx, ty, fstep+1, fiv-1});
else if (fiv == 0) q.push({tx, ty, fstep+1, 0});
}
if (a[tx][ty] == 'X')
if (fiv > 0) q.push({tx, ty, fstep+1, fiv-1});
if (a[tx][ty] == '%')
q.push({tx, ty, fstep+1, k});
}
else
if (fiv > 0) q.push({tx, ty, fstep+1, fiv-1});
}
}
cout << -1;
return;
}
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> a[i][j];
bfs();
return 0;
}