小明又一次陷入了大魔王的迷宫,在无人机的帮忙下,小明获得了整个迷宫的草图。 不同于一般的迷宫,魔王在迷宫里安置了机关,一旦触碰,那么四个方向所在的格子,将翻转其可达性(原先可通过的格子不可通过,反之亦然,机关可以反复触发)。为了防止小明很容易地出逃,魔王在临走前把钥匙丢在了迷宫某处,只有拿到钥匙,小明才能开门在出口处离开迷宫。 万般无奈之下,小明想借助聪明的你,帮忙计算是否有机会离开这个迷宫,最少需要多少时间。(每一单位时间只能向四邻方向走一步)
第一行为 T,表示输入数据组数。 下面 T 组数据,对于每组数据: 第一行是两个数字 n, m(2 < n * m <= 64),表示迷宫的长与宽。 接下来 n 行,每行 m 个字符,‘.’表示空地可以通过,‘x’表示陷阱,‘*’表示机关,‘S’代表起点,‘E’代表出口,‘K’表示钥匙(保证存在且只有一个)。
对第 i 组数据,输出 Case #i: 然后输出一行,仅包含一个整数,表示最少多少步能够拿到钥匙并走出迷魂阵,如果不能则打出-1。
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
Case #1:
11
Case #2:
13
Case #3:
13
Case #4:
11
Case #5:
-1
题目地图范围不大,很容易想到bfs(宽度优先搜索),但是因为有机关可以翻转的存在,在搜索的时候无法保存整图的地图信息(不能在原图信息上更改,会影响其它搜索情况),所以在搜索的结构体中添加一个Status无符号长整型变量(因为地图最多有64个位置所以 S t a t u s ∈ [ 0 , 2 64 ] Status\in[0,2^{64}] Status∈[0,264]),Status的每一位二进制数存储一个地图位置是否翻转的信息,1为翻转,0为没有翻转,这样对照原始地图信息即可获得当前地图信息。
但是这道题如果直接搜索的话因为翻转机关的存在会陷入死循环,所以要对每一步状态判断之前是否达到过这种状态来,若达到过则不必再进行搜索,这里可以利用set容器通过结构体重载来进行去重,也可以建立一个10000大小的hash表,取每个状态坐标、翻转状态、钥匙状态之和模10000作为hash值进行去重。
#include
using namespace std;
const int maxn = 70;
const int mod = 10000;
struct Search {
// 坐标位置和步数
int X, Y, Step;
// 按二进制存全图翻转信息
unsigned long long Status;
// 是否拿到钥匙
bool Key;
Search() {
X = Y = Step = 0;
Status = 0;
Key = false;
}
// 乱搞哈希
int hash() {
return (Status % mod + X + Y + Key) % mod;
}
bool operator == (const Search &a) {
return a.X == X && a.Y == Y && a.Status == Status && a.Key == Key;
}
};
int T;
int N, M;
int StartX, StartY;
int KeyX, KeyY;
int EndX, EndY;
int Ans;
char Plat[maxn][maxn];
queue<Search> Que;
vector<Search> Hash[mod];
bool PosJudge(int X, int Y) {
return X >= 0 && X < N && Y >= 0 && Y < M;
}
void Add(int X, int Y, int Step, unsigned long long Status, bool Key) {
// 哈希去重判断此状态是否走过
Search Judge;
Judge.X = X; Judge.Y = Y; Judge.Step = Step; Judge.Status = Status; Judge.Key = Key;
int Temp = Judge.hash();
for (int i = 0; i < int(Hash[Temp].size()); ++i) {
if (Hash[Temp][i] == Judge) {
return;
}
}
// 若没走过则添加进Hash表以及队列中
Hash[Temp].push_back(Judge);
Que.push(Judge);
}
// 翻转(X,Y)
unsigned long long Convert(int X, int Y, unsigned long long Status) {
for (int i = -1; i <= 1; ++i) {
for (int j = -1; j <= 1; ++j) {
if (abs(i) != abs(j)) {
int NX = X + i, NY = Y + j;
if (PosJudge(NX, NY)) {
Status ^= 1ull << (NX * M + NY);
}
}
}
}
return Status;
}
// 更新
void Update(int X, int Y, int Step, unsigned long long Status, bool Key) {
// 是否翻转
bool Flag = (1ull << (X * M + Y)) & Status;
// 陷阱但是翻转过,可以走
if (Plat[X][Y] == 'x' && Flag) {
Add(X, Y, Step, Status, Key);
}
// 不是陷阱并且没有翻转过,可以走
else if (Plat[X][Y] != 'x' && !Flag) {
// 拿到钥匙,改变Key状态
if (X == KeyX && Y == KeyY) {
Add(X, Y, Step, Status, true);
}
// 到达机关,可以翻转四个方向
else if (Plat[X][Y] == '*') {
Add(X, Y, Step, Convert(X, Y, Status), Key);
}
else {
Add(X, Y, Step, Status, Key);
}
}
}
void Bfs() {
while (!Que.empty()) {
Que.pop();
}
for (int i = 0; i < mod; ++i) {
Hash[i].clear();
}
Search Start;
Start.X = StartX; Start.Y = StartY; Start.Step = 0; Start.Status = 0; Start.Key = false;
Hash[Start.hash()].push_back(Start);
Que.push(Start);
while (!Que.empty()) {
Search Keep = Que.front();
Que.pop();
if (Keep.X == EndX && Keep.Y == EndY && Keep.Key == true) {
Ans = Keep.Step;
return;
}
for (int i = -1; i <= 1; ++i) {
for (int j = -1; j <= 1; ++j) {
if (abs(i) != abs(j)) {
int NX = Keep.X + i, NY = Keep.Y + j;
if (PosJudge(NX, NY)) {
Update(NX, NY, Keep.Step + 1, Keep.Status, Keep.Key);
}
}
}
}
}
}
int main(int argc, char *argv[]) {
scanf("%d", &T);
for (int Case = 1; Case <= T; ++Case) {
scanf("%d%d", &N, &M);
for (int i = 0; i < N; ++i) {
scanf("%s", Plat[i]);
for (int j = 0; j < M; ++j) {
if (Plat[i][j] == 'S') {
StartX = i; StartY = j;
Plat[i][j] = '.';
}
else if (Plat[i][j] == 'E') {
EndX = i; EndY = j;
Plat[i][j] = '.';
}
else if (Plat[i][j] == 'K') {
KeyX = i; KeyY = j;
Plat[i][j] = '.';
}
}
}
Ans = -1;
Bfs();
printf("Case #%d:\n%d\n", Case, Ans);
}
return 0;
}