Java B组蓝桥杯第十届国赛:大胖子走迷宫

38 篇文章 1 订阅
32 篇文章 2 订阅

试题 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

没有找到系统能够全部测试,不确定代码有没有错误。但思路应该没啥大问题。

这一题和第六届国赛的《穿越雷区》很像,可以拿去练手

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值