百度之星2017 初赛 迷宫出逃(HDU 6111)

迷宫出逃

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 15    Accepted Submission(s): 1


Problem Description
小明又一次陷入了大魔王的迷宫,在无人机的帮忙下,小明获得了整个迷宫的草图。


不同于一般的迷宫,魔王在迷宫里安置了机关,一旦触碰,那么四个方向所在的格子,将翻转其可达性(原先可通过的格子不可通过,反之亦然,机关可以反复触发)。为了防止小明很容易地出逃,魔王在临走前把钥匙丢在了迷宫某处,只有拿到钥匙,小明才能开门在出口处离开迷宫。

万般无奈之下,小明想借助聪明的你,帮忙计算是否有机会离开这个迷宫,最少需要多少时间。(每一单位时间只能向四邻方向走一步)
 

Input
第一行为 T,表示输入数据组数。

下面 T 组数据,对于每组数据:

第一行是两个数字 n, m(2 < n * m <= 64),表示迷宫的长与宽。

接下来 n 行,每行 m 个字符,‘.’表示空地可以通过,‘x’表示陷阱,‘*’表示机关,‘S’代表起点,‘E’代表出口,‘K’表示钥匙(保证存在且只有一个)。
 

Output
对第 i 组数据,输出

Case #i:

然后输出一行,仅包含一个整数,表示最少多少步能够拿到钥匙并走出迷魂阵,如果不能则打出-1。
 

Sample Input
  
5 5 7 ...*x.. ...x... xEx.... *x...K. .x*...S 5 7 K..*x.. ...x... xEx.... *x..... .x*...S 5 7 ..K*x.. ..*x*.. xEx.... *x..... .x*...S 5 7 ..K*x.. .*xx*.. *E*.... xx..... .x*...S 4 4 S*.. **.. ...E ...K
 

Sample Output
  
Case #1: 11 Case #2: 13 Case #3: 13 Case #4: 11 Case #5: -1
 

Source

题目分析:
这道题比赛的时候没有一个人AC,正确率0%,各位大佬都没过,我想,可能是因为评测机太卡的原因吧。
其实这道题并没有那么难,因为M*N最大才64,很容易就想到状态压缩,又因为是找到终点的最近距离,那么肯定使用BFS。
状态压缩也很基础,假设N行M列,从第一行第一列是二进制第一位,第一行第二列是二进制第二位,第一行第三列是二进制第三位
,第二行第一列是二进制第M+1位,第x行第y列是二进制的第(x-1)*m+y位。每一位是1表示状态反转,每一位状态是0表示状态正常。
如此,就可以很轻松的,把状态用一个64位的无符号整数储存下来了。
然后有没有钥匙是2种状态,0表示有,1表示没有。
找到出口看钥匙状态是0还是1即可
每一位能不能走看那个位置原本的图,和状态是否改变即可。
那么问题来了,bfs必须要判重,也就是要有一个哈希表,存储这个状态到底有没有被访问过。单纯的反转状态就有2^64次方个,那么,用一个数组去裸存肯定不行。比赛时,我就想到了map,结果超了内存。内存限制32MB,太小了。
然后评测机就一直卡着,然后比赛就这么结束了。
其实,比赛完之后仔细想想,状态绝对不会出现很多,因为图太小了,我就写了个hash表,建立了一个10000的queue数组,取每个状态的坐标,反转状态和钥匙状态的和,mod 10000作为hash值插入相应数组的位置,然后判重时遍历数组即可。
附代码:
#include<iostream>
using namespace std;
#include<cstdio>
#include<queue>
#include<cstring>
#include<vector>
int tx[] = { -1,1,0,0 };//上下左右移动x的改变量 
int ty[] = { 0,0,-1,1 };//上下左右移动y的改变量 
int ii = 0;
typedef unsigned long long LL;
struct datatype//队列中状态信息 
 {
	int bs;//步数 
	LL zt1;//第一个状态,储存每个点的状态是否反转 
	LL zt2;//第二个状态,储存是否有钥匙 
	int x;//所在的x坐标 
	int y;//所在的y坐标 
	datatype()//初始化 
	{
		bs = 0;
		zt1 = 0;
		x = 0;
		y = 0;
		zt2 = 0;
	}
};
queue<datatype> team;//BFS所用的队列 
int maps[70][70];//初始的地图图信息 
struct mapdata {//hash判重所用的信息类型 
	int x, y;//x,y坐标 
	LL zt1;//第一个状态 
	int zt2;//第二个状态 
	LL count()//每个状态的和 mod 10000,取得hash值 
	{
		LL ans = zt1 % 10000;
		ans = (ans + zt2) % 10000;
		ans = (ans + x) % 10000;
		ans = (ans + y) % 10000;
		return ans;
	}
	bool operator<(const mapdata &b)const//重载 
	{
		if (this->x != b.x) return this->x< b.x;
		if (this->y != b.y) return this->y< b.y;
		if (this->zt1 != b.zt1) return this->zt1 < b.zt1;
		return this->zt2 < b.zt2;
	}
	bool operator==(const mapdata &b)const//重载 
	{
		if (this->x != b.x) return this->x == b.x;
		if (this->y != b.y) return this->y == b.y;
		if (this->zt1 != b.zt1) return this->zt1 == b.zt1;
		return this->zt2 == b.zt2;
	}
};
char s[70][100];//最初读入得字符串地图信息 
vector<mapdata> has[10010];//hash判重用到得vector数组 
void deal() 
{
	memset(s, 0, sizeof(s));//初始化 
	memset(maps, 0, sizeof(maps));//初始化 
	int n, m; 
	while (!team.empty()) team.pop();//清空队列 
	datatype pt;//一个读取队列状态的变量 
	cin >> n >> m;//读取n,m,地图长,宽 
	for (int i = 0; i <= 10000; i++)
		has[i].clear();//清空hash表 
	for (int i = 1; i <= n; i++)
	{
		scanf("%s", s[i] + 1);//读取地图信息 
	}
	int qdx, qdy;//保存起点x,y坐标 
	for (int i = 1; i <= n; i++)//处理地图 
	{
		for (int j = 1; j <= m; j++)
		{
			if (s[i][j] == '.') maps[i][j] = 1;//可行为1 
			if (s[i][j] == 'x') maps[i][j] = 0;//不可行为0 
			if (s[i][j] == 'E') maps[i][j] = -666;//出口为-666 
			if (s[i][j] == '*') maps[i][j] = -10;//机关为-10 
			if (s[i][j] == 'K') maps[i][j] = -66;//钥匙为-66 
			if (s[i][j] == 'S') {//找到起点,将起点入队 
				maps[i][j] = 1, qdx = i, qdy = j;
				pt.bs = 0, pt.zt1 = 0, pt.zt2 = 0, pt.x = qdx, pt.y = qdy;
				team.push(pt);
			}
		}
	}
	datatype pd;
	int ans = -1;//初始化答案变量 
	while (!team.empty())//BFS 
	{
		pt = team.front();//取队首元素 
		team.pop();//队首元素出队 
		if (maps[pt.x][pt.y] == -666)//如果是出口,且有钥匙,找到答案,退出搜索 
		{
			if (pt.zt2 == 1)
			{
				ans = pt.bs;
				break;
			}
		}
		for (int i = 0; i < 4; i++)//向当前位置的x,y轴移动 
		{
			pd.bs = pt.bs + 1;//更新步数 
			pd.zt1 = pt.zt1; 
			pd.zt2 = pt.zt2;
			pd.x = pt.x + tx[i];//更新x坐标 
			pd.y = pt.y + ty[i];//更新y坐标 
			if (pd.x <= 0 || pd.y <= 0 || pd.x > n || pd.y > m) continue;//超出边界,舍弃这个位置 
			LL cj = (LL)(m)*(pd.x - 1) + pd.y;//看当前坐标是地图横着数第几个位置,也就是状态压缩后的第几位,式子:(x-1)*m+y 
			LL zt = ((pd.zt1 >> (LL)(cj - 1)) & (LL)1);//看当前坐标是否受机关影响 
			if (zt == 0 && maps[pd.x][pd.y] == 0) continue;//不受影响,原本不可行,舍弃这个位置 
			if (zt == 1) 
			{
				if (maps[pd.x][pd.y] != 0) continue;//受机关影响,原本可行,舍弃这个位置 
			}
			LL ws1, ws2, ws3, ws4;//储存上下左右四点 ,是地图横着数第几个位置,也就是状态压缩后的第几位
			ws1 = 0; ws2 = 0; ws3 = 0; ws4 = 0;
			ws1 = (pd.x - 2)*(m)+pd.y;
			ws2 = (pd.x)*m + (pd.y);
			ws3 = (pd.x - 1)*m + (pd.y - 1);
			ws4 = (pd.x - 1)*m + (pd.y + 1);
			if (maps[pd.x][pd.y] == -10)//如果这个点是机关,就更新上下左右四点的反转状态 
			{
				if (pd.x - 1 > 0)//判断向上走超不超过边界 
				{
					if ((pd.zt1 >> (ws1 - (LL)1)) & (LL)1)//对应位置的翻转状态 
						pd.zt1 = pd.zt1 - ((LL)1 << (ws1 - (LL)1));//是1变成0 
					else
						pd.zt1 = pd.zt1 + ((LL)1 << (ws1 - (LL)1));//是0变成1 
				}
				/*后面的以此类推*/ 
				if (pd.x + 1 <= n)
				{
					if ((pd.zt1 >> (ws2 - (LL)1)) & (LL)1)
						pd.zt1 = pd.zt1 - ((LL)1 << (ws2 - (LL)1));
					else
						pd.zt1 = pd.zt1 + ((LL)1 << (ws2 - (LL)1));
				}
				if (pd.y - 1 > 0)
				{
					if ((pd.zt1 >> (ws3 - (LL)1)) & (LL)1)
						pd.zt1 = pd.zt1 - ((LL)1 << (ws3 - (LL)1));
					else
						pd.zt1 = pd.zt1 + ((LL)1 << (ws3 - (LL)1));
				}
				if (pd.y + 1 <= m)
				{
					if ((pd.zt1 >> (ws4 - (LL)1)) & (LL)1)
						pd.zt1 = pd.zt1 - ((LL)1 << (ws4 - (LL)1));
					else
						pd.zt1 = pd.zt1 + ((LL)1 << (ws4 - (LL)1));
				}
			}
			if (maps[pd.x][pd.y] == -66)//该点存在钥匙,钥匙状态设为1 
			{
				pd.zt2 = 1;
			}
			mapdata h;//建立一个hash信息的变量 
			h.x = pd.x; h.y = pd.y; h.zt1 = pd.zt1; h.zt2 = pd.zt2;//变量赋值 
			int zzt = h.count();//计算hash值 
			int vvv = 0;//判断访问与否的变量 
			for (int k = 0; k < has[zzt].size(); k++)//遍历所在hash表中的节点 
			{
				if (has[zzt][k] == h)//该状态已存在,标记已访问 
				{
					vvv = 1;
					break;
				}
			}
			if (vvv) continue;//如果标记访问,舍弃该状态 
			has[zzt].push_back(h);//未访问,hash信息入表 
			team.push(pd);//状态信息入队 
		}
	}
	/*输出答案*/ 
	printf("Case #%d:\n", ii); 
	cout << ans << endl;
}
int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		ii++;
		deal();
	}
	return 0;
}



  • 3
    点赞
  • 1
    收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 9

打赏作者

xfww123

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值