地牢逃脱
题目
- 给定一个 n 行 m 列的地牢,其中
'.'
表示可以通行的位置,'X'
表示不可通行的障碍,牛牛从( x 0 , y 0 x_0,y_0 x0,y0 ) 位置出发,遍历这个地牢。- 和一般的游戏所不同的是,他每一步只能按照一些指定的方式遍历地牢,要求每一步都不可以超过地牢的边界,也不能到达障碍上。
- 地牢的出口可能在任意某个可以通行的位置上。牛牛想知道
出口在最坏情况
下,他最少需要多少步
才可以离开这个地牢。
输入描述:
- 每个输入包含 1 个测试用例。每个测试用例的第一行包含两个整数 n , m n,m n,m( 1 ≤ n ≤ 50 ,      1 ≤ m ≤ 50 1 \leq n \leq 50,\;\;1 \leq m \leq 50 1≤n≤50,1≤m≤50),表示地牢的长和宽。
- 接下来的 n 行,每行 m 个字符,描述地牢,地牢将至少包含两个
'.'
。- 接下来的一行,包含两个整数$ x_0,y_0$,表示牛牛的出发位置( 0 ≤ x 0 < n ,      0 ≤ y 0 < m 0 \leq x_0 < n,\;\; 0 \leq y_0 < m 0≤x0<n,0≤y0<m),左上角的坐标为 (0, 0),出发位置一定是
'.'
)。- 之后的一行包含一个整数 k( 0 < k ≤ 50 0 < k \leq 50 0<k≤50)表示牛牛合法的步长数,接下来的 k 行,每行两个整数 dx, dy 表示每次可选择移动的行和列步长( − 50 ≤ d x , d y ≤ 50 -50 \leq dx, dy \leq 50 −50≤dx,dy≤50)
输出描述:
- 输出一行一个数字表示最坏情况下需要多少次移动可以离开地牢,如果永远无法离开,输出 -1。以下测试用例中,牛牛可以上下左右移动,在所有可通行的位置.上,地牢出口如果被设置在右下角,牛牛想离开需要移动的次数最多,为3次。
输入示例:
3 3
...
...
...
0 1
4
1 0
0 1
-1 0
0 -1
输出示例:
3
解题思路
题目难点
- 清楚的理解题目意思,其中出口是任意可到达的坐标点, 因此需要求出出发点到达任意出口点的最少步数。最后取最大的那个出口做为答案。
- 如何利用行走方式列表,该列表是判断其它坐标点是否是当前位置周围点的条件(BFS广度优先算法)
- 充分理解广度优先算法的实际应用(BFS实际上是一种逐层向外扩张的算法)
- 如何避免无限迭代(设置一个访问过的标志位即可)
- 该题目中的坐标轴和平时所遇到的坐标轴不一样
问题图解
地牢逃脱坐标系统
地牢逃脱问题示意图
广度优先算法图示
代码
#include <iostream>
#include <utility>
#include <vector>
#include <queue>
using namespace std;
class Point;
// 全局变量初始化
static vector< vector<Point> > grid; // 坐标网格
static queue<Point> help_queue; // 辅助队列
static vector< pair<int, int> > methods; // 行走方式列表
static unsigned int n = 0; // 坐标网格的x长度
static unsigned int m = 0; // 坐标网格的y长度
static unsigned int k = 0; // 行走方式的种数
class Point
{
// 成员变量
public:
int x; // 坐标点的x坐标
int y; // 坐标点的y坐标
int step; // 从出发点到达该坐标点所花费的步长
char reachable; // 是否可到达
bool visited; // 是否访问过
public:
Point():x(0), y(0), step(-1), reachable('.'), visited(false){}
// 类方法判断一个位置(x, y)是否合法(不超过边界)
static bool isValid(int x, int y)
{
if( x < 0 || x >= n || y < 0 || y >= m)
return false;
else
return true;
}
// 判断一个坐标点的位置是否可以到达
bool isReachable()
{
if( reachable == 'X')
return false;
else
return true;
}
// 判断一个坐标点的位置是否访问过
bool isVisited()
{
return visited;
}
void pushArround()
{
// 下一步坐标点的位置(next_x, next_y)
int next_x, next_y;
// methods是行走方式列表
// methods[idx]为其中一种行走方式
// methods[idx].first是行走方式的x位移
// methods[idx].second是行走方式的y位移
for(unsigned int idx=0; idx < k; ++idx)
{
next_x = x + methods[idx].first;
next_y = y + methods[idx].second;
if( isValid(next_x, next_y)
&& grid[next_x][next_y].isReachable()
&& !grid[next_x][next_y].isVisited()){
grid[next_x][next_y].step = step+1; // 下一步坐标点所花费的步数 = 当前坐标点所花费的步数+1
grid[next_x][next_y].visited = true; // 下一步坐标点设置为访问过
help_queue.push(grid[next_x][next_y]); // 压入辅助队列
}
}
}
};
int main()
{
cin >> n >> m;
// 生成n * m 二维网格
grid.resize(n);
for(unsigned int idx=0; idx < n; ++idx){
grid[idx].resize(m);
}
// 输入坐标点是否可达
for(unsigned int idx_x=0; idx_x < n; ++ idx_x){
for(unsigned int idx_y=0; idx_y < m; ++idx_y){
cin >> grid[idx_x][idx_y].reachable;
grid[idx_x][idx_y].x = idx_x;
grid[idx_x][idx_y].y = idx_y;
}
}
// 输入出发点的坐标(x0, y0)
int x0, y0;
cin >> x0 >> y0;
// 设置出发点的属性,并在迭代之前将其压入队列
grid[x0][y0].step = 0;
grid[x0][y0].visited = true;
help_queue.push(grid[x0][y0]);
// 输入行走方式的列表
cin >> k;
for(unsigned int idx=0; idx < k; ++idx){
int dx, dy;
cin >> dx >> dy;
methods.emplace_back(dx, dy);
}
// 开始BFS算法
while( !help_queue.empty() ){
Point temp_point = help_queue.front();
help_queue.pop();
temp_point.pushArround();
}
// 遍历整个坐标网格得出结果
int result = 0;
for(unsigned int idx_x=0; idx_x < n; ++idx_x){
for(unsigned int idx_y=0; idx_y < m; ++idx_y){
if(grid[idx_x][idx_y].isReachable() ){
if(grid[idx_x][idx_y].step==-1){
// 如果存在出口永远不可到达,则直接跳出两层循环
result = -1;
idx_x = n;
break;
} else {
// 否则则取所花费步数最长的那个出口
result = max(result, grid[idx_x][idx_y].step);
}
}
}
}
cout << result << endl;
}