这题的一个做法是先对 'F' BFS一遍,这样就可以记录下来火势到达每一个格子最早的时间,对于每个 '.' 来说,在这个火到达的最早时间之前,joe是可以走的。然后对joe BFS 一遍就可以了,重点是joe在”边上“,也就是最左边最右边最上边最下边。
特别的是,我在bfs中采用的是每产生一个新的节点就判断一下是不是终点,所以对于joe一开始就在边上的情况需要额外判断。
#include<cstdio>
#include<climits>
#include<vector>
#include<queue>
using namespace std;
struct state
{
int x, y, th;//th表示这是第几分钟
};
const int direction[][2] = { { 0,1 },{ 0,-1 },{ 1,0 },{ -1,0 } };
int r, c, rj, cj,time[1000][1000];
char maze[1000][1002];
bool vis[1000][1000];//记录是否被访问过
vector<pair<int, int>> fire;
queue<state> q;
inline bool inlaw(int a, int b)
{
return a >= 0 && b >= 0 && a < r&&b < c&&maze[a][b] == '.';
}
void burn()
{
for (int i = 0; i < r; i ++ )
for (int j = 0; j < c; j++)
if (maze[i][j] =='.') time[i][j] = INT_MAX;
while (!q.empty()) q.pop();
for (int i = 0; i < fire.size(); i++)
{
q.push({ fire[i].first,fire[i].second,0 });
time[fire[i].first][fire[i].second] = 0;
}
int nx, ny;
while (!q.empty())
{
state t = q.front(); q.pop();
for (int i = 0; i < 4; i++)
{
nx = t.x + direction[i][0];
ny = t.y + direction[i][1];
if (inlaw(nx, ny) && time[nx][ny] == INT_MAX)
{
time[nx][ny] = time[t.x][t.y] + 1;
q.push({ nx,ny,0 });
}
}
}
}
int bfs()
{
while (!q.empty()) q.pop();
q.push({ rj,cj,0 });
while (!q.empty())
{
state t = q.front(); q.pop();
for (int i = 0; i < 4; i++)
{
int nx = t.x + direction[i][0], ny = t.y + direction[i][1];
if (inlaw(nx,ny)&& !vis[nx][ny]&&t.th+1<time[nx][ny])
{
if (!nx || !ny || nx == r - 1 || ny == c - 1) return t.th + 2;
//+2是因为需要从上一个位置走到边界,再走出迷宫
q.push({ nx,ny,t.th + 1 });
vis[nx][ny] = true;
}
}
}
return 0;
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d%d", &r,&c);
fire.clear();
for (int i = 0; i < r; i++)
{
scanf("%s", maze[i]);
for (int j = 0; j < c; j++)
{
if (maze[i][j] == 'J')
{
rj = i;
cj = j;
maze[i][j] = '.';
}
if (maze[i][j] == '.') vis[i][j] = false;
else if (maze[i][j] == 'F') fire.push_back({ i,j });
}
}
if (!rj || !cj || rj == r - 1 || cj == c - 1)
{
printf("1\n");
continue;
}
burn();
int t = bfs();
if (t) printf("%d\n", t);
else printf("IMPOSSIBLE\n");
}
return 0;
}
其实我本来用的是一种效率很低的土方法:
因为火可以蔓延到所有相邻的空地,但是joe不能走到火将在下一分钟蔓延到的地方,所以在对joe BFS每一层之前,先把火在这一分钟到的地方变成 ’F’。我的土土的实现方式是先记录下来所有初始的火位置,然后把下一分钟产生的火的位置记为 ‘F' ,同时记录下来新火的位置,因为只有这些火才能产生新的火。效率很低,但毕竟是我自己想的,记录一下
//这个版本使用了数组手动模拟的队列
#include<iostream>
#include<set>
using namespace std;
struct state
{
int x, y, th;
};
const int direction[][2] = { {0,1},{0,-1},{1,0},{-1,0} };
state q[1000000];
int r, c,rj,cj,fire[50000][2],nfnum;
char maze[1000][1002];
bool vis[1000][1000];
inline bool inlaw(int a, int b)
{
return a >= 0 && b >= 0 && a < r&&b < c&&maze[a][b]=='.';
}
void burn()
{
set<pair<int, int>> t;
int nx, ny;
for(int i=0;i<nfnum;i++)
for (int j = 0; j < 4; j++)
{
nx = fire[i][0]+direction[j][0], ny = fire[i][1]+direction[j][1];
if (inlaw(nx, ny)) t.insert({ nx,ny });
}
nfnum = 0;
for (set<pair<int, int>>::iterator it = t.begin(); it != t.end(); it++)
{
maze[(*it).first][(*it).second] = 'F';
fire[nfnum][0] = (*it).first;
fire[nfnum][1] = (*it).second;
nfnum++;
}
}
int bfs()
{
int cur = 0, front = 0, rear = 1;
state t;
q[front].x = rj; q[front].y = cj;
q[front].th = 0;
while (front<rear)
{
t.x = q[front].x;
t.y = q[front].y;
t.th = q[front].th;
front++;
if (t.th != cur - 1)
{
cur++;
burn();
}
for (int i = 0; i < 4; i++)
{
int nx = t.x + direction[i][0], ny = t.y + direction[i][1];
if (inlaw(nx,ny)&& !vis[nx][ny])
{
if (!nx || !ny || nx == r - 1 || ny == c - 1) return t.th + 2;
q[rear].x = nx;
q[rear].y = ny;
q[rear].th = t.th + 1;
rear++;
vis[nx][ny] = true;
}
}
}
return 0;
}
int main()
{
int T;
cin >> T;
while (T--)
{
cin >> r >> c;
nfnum = 0;
for (int i = 0; i < r; i++)
{
cin >> maze[i];
for (int j = 0; j < c; j++)
{
if (maze[i][j] == 'J')
{
rj = i;
cj = j;
maze[i][j] = '.';
}
if (maze[i][j] == '.') vis[i][j] = false;
else if (maze[i][j] == 'F')
{
fire[nfnum][0] = i;
fire[nfnum][1] = j;
nfnum++;
}
}
}
if (!rj || !cj || rj == r - 1 || cj == c - 1)
{
cout << 1 << endl;
continue;
}
int t = bfs();
if (t) cout << t << endl;
else cout << "IMPOSSIBLE" << endl;
}
return 0;
}