2020牛客寒假算法基础集训营5.G——街机争霸【BFS & 思维】

题目传送门


题目描述

哎,又是银首,要是你这个签到题少WA一发就金了

牛牛战队的队员打完比赛以后又到了日常甩锅的时间。他们心情悲伤,吃完晚饭以后,大家相约到一个街机厅去solo。牛牛和牛能进入了一个迷宫,这个迷宫里除了墙壁的阻拦,还会有僵尸的阻拦。情况十分复杂,牛能为了更快的追逐牛牛,迅速放出了大招,让牛牛原地眩晕,而眩晕的解药,也只有牛能自己拥有。

这一个迷宫可以简化为一个n行m列的矩阵,其中有一些僵尸,这些僵尸会在一个 1 k 1*k 的矩形中来回游走。他不会攻击眩晕状态下的人,只会攻击和他抢地盘的人。这名队员每次移动需要一个单位时间,而且他不能穿墙,不能和僵尸处于同一位置,在追到另一名队员之前也不能停下来。

在这一场追逐战中,要么牛能追逐成功,取得胜利,要么被僵尸击败,牛牛胜利。那谁会是最终的王者呢?


输入描述:

输入数据有很多行。第一行输入 4 4 个整数 n , m , p , k ( 3 n , m 500 , 0 p 50 , 2 k 10 ) n,m,p,k (3 \leq n,m \leq 500, 0 \leq p \leq 50, 2 \leq k \leq 10) ,分别表示迷宫的行、列,僵尸的数量,僵尸来回走动的长度。

2 2 行到第 n + 1 n+1 行输入一个矩阵,每行输入一个字符串,第ii个字符串的第jj个字符表示矩阵中第 i i j j 列的状态,如果字符是#表示是可以走的路,如果是&表示是障碍物,A是被眩晕队员的位置,L是追赶者的位置。

n + 2 n+2 行到第 n + p + 1 n+p+1 行每行输入两个整数 x , y x,y 和一个字符串,第i行的数据表示第ii个僵尸当前时间会从第 x x y y 列出发,沿着固有的方向前进 k k 个单位时间后折返,再走回它之前的位置,再折返,依照这种方法循环下去。第三个字符串表示僵尸初始行进的方向,UP表示向上走,LEFT表示向左走,DOWN表示向下走,RIGHT表示向右走。数据保证在 k k 个长度内僵尸不会碰到边界或者墙壁。


输出描述:

如果牛能可以追上牛牛,输出一个整数,表示最短追上的时间。否则输出一行Oh no。


输入

3 3 1 3
&&A
###
&&L
2 1 RIGHT


输出

2

扫描二维码关注公众号,回复: 9151371 查看本文章

说明

如果用*代表僵尸的位置,则起始时状态为

&&A
*##
&&L

一个单位时间后,牛能上走,僵尸向右走,两者并没有碰上

&&A
#*L
&&#

再一个单位时间后,牛能向上走,追上牛牛,取得胜利。

&&L
##*
&&#


输入

4 4 1 2
L#&A
##&#
#&##
####
3 3 DOWN


输出

Oh no


说明

初始状态

L#&A
##&#
#&*#

一单位时间后

##&A
L#&#
#&##
##*#

二单位时间后

##&A
##&#
L&*#
####

三单位时间后

##&A
##&#
#&##
L#*#

四单位时间后

##&A
##&#
#&*#
#L##

牛能如果再向左走的话就会跟僵尸碰个正着,而且不论牛能怎么往回走,在(4,3)总能遇见僵尸,所以他失败了。


题解

  • 如果不考虑僵尸,这是一道很简单的BFS搜索
  • 注意题目的条件
    1. 僵尸有统一的步伐
    2. 僵尸移动过程中不会遇到墙面提前返回
  • 有了这俩条件,题目就变得简单多了
  • 我们思考,既然都有统一的步伐,那么也就是说,对于某一个僵尸 在 ( x , y ) (x,y) 位置,初始向下移动,行走长度为 k k ,那么我们知道,任意 2 ( k 1 ) 2(k-1) 的整数倍时间后,僵尸都回到 ( x , y ) (x,y) 的初始位置。
  • 同理,僵尸第一步向下走到达 ( x + 1 , y ) (x+1,y) 的位置,那么经过任意 2 ( k 1 ) 2(k-1) 整数倍后,僵尸还是在这里
  • 一次类推,我们可以通过僵尸起始位置、行走长度、初始移动方向,得到这个僵尸在任意时间所到达的位置(对周期数取模)
  • 但是我们思考,僵尸路径是徘徊的,也就是这样: A B C B A B C B A . . . A\to B\to C\to B\to A\to B \to C\to B\to A \to...
  • 我们知道周期数是 2 ( k 1 ) 2(k-1) (注意,这里是k-1的原因是:路径长度是 k k ,但是初始时刻占据了一个格子,不需要花费时间移动,需要时间移动到达的共有 k 1 k-1 个格子)
  • (考虑每一次到达 B B 位置)每 2 ( k 1 ) 2(k-1) 周期的时间,我们只是从 A B A \to B ,实际上我们到达 B B 位置还可以从 C B C \to B
  • 也就是说,到达某一个点是两个方向的周期 (看代码注释)
  • 现在,我们有了僵尸在任意时间到达的位置,那么我们可以用 f u c k fuck数组 手动滑稽)记录某一位置 ( x , y ) (x,y) t t 时刻 有僵尸到达。 f u c k (见fuck数组)
  • 处理好了僵尸,我们来看追梦人(牛能)移动的方案
  • 显然不能直接标记通过的位置,因为其实走过的路径是可以返回的。可能我们遇到了僵尸,因为不可以停止,那么可能我们在附近溜达一会,等僵尸走了,就可以通过了。因为存在这种状态,所以不可以直接标记。
  • 我们来思考,移动的方案其实是完全取决于僵尸的,因为所有的僵尸共用一个周期,那么很容易想到,追梦人移动的方案其实就是为了躲避僵尸移动的周期。
  • 那么按照僵尸的标记方式,标记追梦人移动过的位置即可。 v i s (见vis数组)
  • 其实这道题始终围绕着的都是僵尸的周期数,想明白这点就很好做了。

AC-Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 1e5 + 7;
const double eps = 1e-7;

int n, m, p, k;
int mod; // 周期数
string mp[505];
struct Node {
	int x, y, t;
	Node() { x = y = t = 0; }
	Node(int x, int y, int t) :x(x), y(y), t(t) {}
};
int dx[] = { -1,1,0,0 }; // 依次:上下左右
int dy[] = { 0,0,-1,1 };
int vis[505][505][22]; // 避免和 僵尸周期 相同的 周期徘徊 状态
bool fuck[505][505][22]; // (i,j)位置在k时刻有僵尸
bool check(int x, int y, int t) {
	if (x < 0 || x >= n)	return false;
	if (y < 0 || y >= m)	return false;
	if (mp[x][y] == '&')	return false;
	if (fuck[x][y][t % mod])	return false; // 没有僵尸
	if (vis[x][y][t % mod])	return false; // 没走过
	return true;
}
int bfs(Node start) {
	queue<Node> q;
	q.push(start);
	vis[start.x][start.y][0] = true;
	while (!q.empty()) {
		Node now = q.front();
		q.pop();
		if (mp[now.x][now.y] == 'A') 
			return now.t;
		for (int i = 0; i < 4; ++i) {
			int nx = now.x + dx[i];
			int ny = now.y + dy[i];
			int nt = now.t + 1;
			if (check(nx, ny, nt)) {
				q.push(Node(nx, ny, nt));
				vis[nx][ny][nt % mod] = true;
			}
		}
	}
	return 0;
}
void init() {
	memset(fuck, false, sizeof fuck);
	memset(vis, false, sizeof vis);
}
int main() {
	while (cin >> n >> m >> p >> k) {
		init();
		mod = 2 * (k - 1);
		Node st(0, 0, 0); // 起始点
		for (int i = 0; i < n; ++i) {
			cin >> mp[i];
			for (int j = 0; j < m; ++j)
				if (mp[i][j] == 'L')
					st.x = i, st.y = j;
		}
		for (int i = 0; i < p; ++i) {
			int x, y;	cin >> x >> y;	--x, --y; // 输入数据从1开始,代码从0开始
			fuck[x][y][0] = true; // 记录僵尸起始位置
			string s;	cin >> s;
			int dst = 0; // 记录僵尸移动方向
			if (s[0] == 'U')	dst = 0;
			else if (s[0] == 'D') dst = 1;
			else if (s[0] == 'L') dst = 2;
			else if (s[0] == 'R') dst = 3;
			for (int j = 1; j < k; ++j) {
				x += dx[dst], y += dy[dst]; // 当前僵尸下一个位置
				fuck[x][y][j % mod] = true; // 僵尸在第j秒会到达这个位置 正方向
				fuck[x][y][mod - j] = true; // 僵尸在第j秒会到达这个位置 反方向
			}
		}
		int ans = bfs(st);
		if (ans)	 cout << ans << endl;
		else	cout << "Oh no" << endl;
	}
}
发布了186 篇原创文章 · 获赞 116 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Q_1849805767/article/details/104313572