试题 H: 大胖子走迷宫
时间限制: 1.0s 内存限制: 512.0MB 本题总分:20 分
【问题描述】
小明是个大胖子,或者说是个大大胖子,如果说正常人占用 1 × 1 的面积,
小明要占用 5 × 5 的面积。
由于小明太胖了,所以他行动起来很不方便。当玩一些游戏时,小明相比
小伙伴就吃亏很多。
小明的朋友们制定了一个计划,帮助小明减肥。计划的主要内容是带小明
玩一些游戏,让小明在游戏中运动消耗脂肪。走迷宫是计划中的重要环节。
朋友们设计了一个迷宫,迷宫可以看成是一个由 n × n 个方阵组成的方阵,
正常人每次占用方阵中 1 × 1 的区域,而小明要占用 5 × 5 的区域。小明的位置
定义为小明最正中的一个方格。迷宫四周都有障碍物。
为了方便小明,朋友们把迷宫的起点设置在了第 3 行第 3 列,终点设置在
了第 n − 2 行第 n − 2 列。
小明在时刻 0 出发,每单位时间可以向当前位置的上、下、左、右移动单
位 1 的距离,也可以停留在原地不动。小明走迷宫走得很辛苦,如果他在迷宫
里面待的时间很长,则由于消耗了很多脂肪,他会在时刻 k 变成一个胖子,只
占用 3 × 3 的区域。如果待的时间更长,他会在时刻 2k 变成一个正常人,只占
用 1 × 1 的区域。注意,当小明变瘦时迷宫的起点和终点不变。
请问,小明最少多长时间能走到迷宫的终点。注意,小明走到终点时可能
变瘦了也可能没有变瘦。
【输入格式】
输入的第一行包含两个整数 n,k。
接下来 n 行,每行一个由 n 个字符组成的字符串,字符为 + 表示为空地,
字符为 * 表示为阻碍物。
【输出格式】
输出一个整数,表示答案。
【样例输入】
9 5
+++++++++
+++++++++
+++++++++
+++++++++
+++++++++
***+*****
+++++++++
+++++++++
+++++++++
【样例输出】
16
【评测用例规模与约定】
对于 30% 的评测用例,1 ≤ n ≤ 50。
对于 60% 的评测用例,1 ≤ n ≤ 100。
对于所有评测用例,1 ≤ n ≤ 300,1 ≤ k ≤ 1000。
这样的题都是老套路题了,相对容易,基本上都是一个广度优先搜索,解决问题。
简单说一下思路:
(1)创建一个队列,将初始位置加入队列。
(2)进入while(队列是否为空)的循环,取出队头,更新时间,胖瘦,然后将其上下左右能移动的位置加入队列。
(3)细节:
a. 如何记录坐标,同步时间,胖瘦程度,还原路线(这里没用到)?
答:用一个自定义类,从队列中取出该类对象,先更新它的时间,胖瘦,然后创建新的位置节点,继承它的时间,胖瘦,并将其添加到 parent的位置。
//位置节点
class Point {
int x;//横坐标
int y;//纵坐标
int time;//时间
Point parent;//从哪个位置节点来的
int a;//肥胖程度
public Point(int x1, int y1, Point p,int t,int a1) {
this.x = x1;
this.y = y1;
this.parent = p;
this.time=t;
this.a=a1;
}
}
还原路线:相当于链表
Point p;
while (p.parent != null) {
p = p.parent;
}
b. 如何处理小明原地等待的情况?
判断他上下左右存在空位置,但是无法移动过去。因为太胖了,因此,如果存在这样的情况,就更新时间胖瘦,重新加入队列。
基本上就这些。
代码:
import java.util.Scanner;
import java.util.concurrent.ArrayBlockingQueue;
public class Main {
int n;//n行n列
int k;//每k个小时小明瘦一圈
char[][] maze;//迷宫地图
boolean[][] vis;//地图访问标记
ArrayBlockingQueue<Point> queue;//队列
//位置节点
class Point {
int x;//横坐标
int y;//纵坐标
int time;//时间
Point parent;//从哪个位置节点来的
int a;//将肥胖转换成比常人的厚度,最开始为2,方便计算坐标
public Point(int x1, int y1, Point p,int t,int a1) {
this.x = x1;
this.y = y1;
this.parent = p;
this.time=t;
this.a=a1;
}
}
public Main() {
Scanner sn = new Scanner(System.in);
n = sn.nextInt();
k = sn.nextInt();
maze = new char[n][n];
vis = new boolean[n][n];
sn.nextLine();
queue = new ArrayBlockingQueue<Point>(n * n);
for (int i = 0; i < n; i++) {
maze[i] = sn.nextLine().trim().toCharArray();
}
//以上都是初始化
queue.add(new Point(2, 2, null,0,2));//加入初始点
vis[2][2] = true;//标记
Point p=bfs();//获取到达终点的那个节点
System.out.println(p.time);//输出时间
// //展示逆推路线
// while (p.parent != null) {
// System.out.println("x="+p.x+",y="+p.y+",time="+p.time+",a="+p.a);
// p = p.parent;
// }
// System.out.println("x="+p.x+",y="+p.y+",time="+p.time+",a="+p.a);
}
//广度优先搜索
public Point bfs() {
while (!queue.isEmpty()) {
Point p = queue.poll();//取出队头
//更新时间,胖瘦
p.time++;
if (p.time % k == 0 && p.a > 0)
p.a--;
//判断这个节点是不是已经是终点了
if (p.x == n - 3 && p.y == n - 3)
return p;
//将四个方向可以移动的位置加入队列
// 下
if (check(p.x, p.y + 1,p) && !vis[p.y + 1][p.x]) {
queue.add(new Point(p.x, p.y + 1, p,p.time,p.a));
vis[p.y + 1][p.x] = true;
}
// 右
if (check(p.x + 1, p.y,p) && !vis[p.y][p.x + 1]) {
queue.add(new Point(p.x + 1, p.y, p,p.time,p.a));
vis[p.y][p.x + 1] = true;
}
// 左
if (check(p.x - 1, p.y,p) && !vis[p.y][p.x - 1]) {
queue.add(new Point(p.x - 1, p.y, p,p.time,p.a));
vis[p.y][p.x - 1] = true;
}
// 上
if (check(p.x, p.y - 1,p) && !vis[p.y - 1][p.x]) {
queue.add(new Point(p.x, p.y - 1, p,p.time,p.a));
vis[p.y - 1][p.x] = true;
}
//如果存在空地但又无法移动,说明小明太胖了,重新加入队列,相当于等待
if(exist(p.x, p.y)) {
queue.add(p);
}
}
//到不了终点,返回空
return null;
}
// 判断p是否能移动到(x,y)
public boolean check(int x, int y,Point p) {
//x是否越界
if (x - p.a >= 0 && x + p.a < n) {
//y是否越界
if (y - p.a >= 0 && y + p.a < n) {
//x,y都正常,因此检查若移动后自身范围是否存在障碍物,若存在则无法移动
for (int i = x - p.a; i <= x + p.a; i++) {
for (int j = y - p.a; j <= y + p.a; j++) {
if (maze[j][i] == '*')
return false;
}
}
//全部通过检查,可以移动
return true;
}
}
//未通过检查
return false;
}
//检查上下左右当前还有空位
public boolean exist(int x, int y) {
//左
if (x - 1 >= 0) {
if (!vis[x - 1][y]) {
return true;
}
}
//上
if (y - 1 >= 0) {
if (!vis[x][y - 1]) {
return true;
}
}
//下
if (y + 1 < n) {
if (!vis[x][y + 1]) {
return true;
}
}
//右
if (x + 1 < n) {
if (!vis[x + 1][y]) {
return true;
}
}
return false;
}
public static void main(String[] args) {
new Main();
}
}
样例测试结果:(16是答案,其他的是逆推路线,以及时间,和胖瘦(a在代码注释中有解释))
16
x=6,y=6,time=16,a=0
x=5,y=6,time=15,a=0
x=4,y=6,time=15,a=0
x=3,y=6,time=13,a=0
x=3,y=5,time=12,a=0
x=3,y=4,time=11,a=0
x=3,y=3,time=10,a=0
x=3,y=2,time=11,a=0
x=2,y=2,time=5,a=1
没有找到系统能够全部测试,不确定代码有没有错误。但思路应该没啥大问题。
这一题和第六届国赛的《穿越雷区》很像,可以拿去练手