一直改bug到现在,幸亏最终还是过了,不然真就通宵了,,,,室友都睡了,抓紧时间发文章,坚持一天一篇。虽然今天已经过了12点,但是没睡觉还是算今天的。哈哈哈哈,不知道自己在说什么。
两次bfs,不能把人和箱子写在一个结构体看成一个整体状态,因为要保证箱子移动最少的前提下,人移动最少。因此先对箱子做bfs,相应的对人做bfs,确实有点难,一整个看不懂可以一部分一部分的看,首先把问题拆解,第一个问题是一个人从start位置开始走到end位置,这是一个经典又简单的bfs,然后写箱子从start到end,但要通过人的bfs判断是否箱子可以有这样的走法。然后反向求出路径,再将路径倒序输出就是结果了。
#include<iostream>
#include<queue>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int,int> PII;
const int N=25,M=25;
int dx[4]= {-1,1,0,0},dy[4]= {0,0,-1,1};
int n,m;
char g[N][M];
char ops[5]="nswe";
int pre_man[N][M];
PII dist[N][M][4];
vector<int> path[N][M][4];
bool st[N][M][4],vis[N][M];
struct Point {
int x,y,dir;
} pre[N][M][4];
bool check(int a,int b) {
return a>=1&&a<=n&&b>=1&&b<=m&&g[a][b]!='#';
}
int bfs_man(PII start,PII end,PII box,vector<int>&seq) {
memset(vis,0,sizeof(vis));
memset(pre_man,-1,sizeof(pre_man));
queue<PII>q;
q.push(start);
vis[start.first][start.second]=true;
vis[box.first][box.second]=true;
while(q.size()) {
PII t=q.front();
q.pop();
int x=t.first,y=t.second;
if(t==end) {
seq.clear();
while(~pre_man[x][y]) {
int d=pre_man[x][y];
seq.push_back(d);
x-=dx[d],y-=dy[d];
}
return seq.size();
}
for(int i=0; i<4; i++) {
int cx=x+dx[i],cy=y+dy[i];
if(!check(cx,cy))continue;
if(vis[cx][cy])continue;
vis[cx][cy]=true;
pre_man[cx][cy]=i;
q.push({cx,cy});
}
}
return -1;
}
bool bfs_box(PII start,PII box,Point &end) {
memset(st,0,sizeof(st));
queue<Point>q;
//因为从Point这个结构体中可以推断出此时箱子和人的距离
//但是第一次的人的位置根据结构体无法推出,所以把第一次箱子移动后的状态都放入队列中
for(int i=0; i<4; i++) {
int a=box.first+dx[i],b=box.second+dy[i];//箱子
int c=box.first-dx[i],d=box.second-dy[i];//人
if(!check(a,b)||!check(c,d))continue;
vector<int>seq;
int len=bfs_man(start, {c,d},box,seq);
if(len==-1)continue;
path[a][b][i]=seq;
st[a][b][i]=true;
dist[a][b][i]= {1,len};
pre[a][b][i]= {box.first,box.second,-1}; //设为-1是为了之后遍历时dir为-1代表箱子移动第一步
q.push({a,b,i});
}
PII mind= {1e9,1e9};
while(q.size()) {
Point t=q.front();
q.pop();
if(g[t.x][t.y]=='T'&&mind>dist[t.x][t.y][t.dir]) {
end=t;
mind=dist[t.x][t.y][t.dir];
}
for(int i=0; i<4; i++) {
int a=t.x+dx[i],b=t.y+dy[i];//箱子
int c=t.x-dx[i],d=t.y-dy[i];//人
if(!check(a,b)||!check(c,d))continue;
vector<int>seq;
int len=bfs_man({t.x-dx[t.dir],t.y-dy[t.dir]}, {c,d}, {t.x,t.y},seq);
if(len==-1)continue;
PII curd= {dist[t.x][t.y][t.dir].first+1,dist[t.x][t.y][t.dir].second+len};
if(!st[a][b][i]) {
path[a][b][i]=seq;
st[a][b][i]=true;
dist[a][b][i]=curd;
pre[a][b][i]=t;
q.push({a,b,i});
} else if(dist[a][b][i]>curd) {
path[a][b][i]=seq;
dist[a][b][i]=curd;
pre[a][b][i]=t;
}
}
}
return mind.first!=1e9;
}
int main(void) {
int T=1;
while(T) {
scanf("%d%d",&n,&m);
if(!n&&!m) {
break;
}
for(int i=1; i<=n; i++) {
cin>>g[i]+1;
}
PII start,box;
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++) {
if(g[i][j]=='S')start= {i,j};
else if(g[i][j]=='B')box= {i,j};
}
}
Point end;
string res;
if(!bfs_box(start,box,end))res="Impossible.";
else {
while(~end.dir) {
res+=ops[end.dir]-32;
for(int i=0; i<path[end.x][end.y][end.dir].size(); i++) {
res+=ops[path[end.x][end.y][end.dir][i]];
}
end=pre[end.x][end.y][end.dir];
}
reverse(res.begin(),res.end());
}
printf("Maze #%d\n",T);
cout<<res<<endl<<endl;
T++;
}
return 0;
}
-
代码包含了一些标准的C++库,如
<iostream>
、<queue>
、<cstring>
、<vector>
和<algorithm>
。 -
定义了常量
N
和M
,它们的值为25。这些常量用于指定迷宫的最大尺寸。 -
定义了数组
dx
和dy
,表示四个可能的移动方向(北、南、西、东)。 -
声明了变量
n
和m
,用于存储迷宫的尺寸。 -
定义了字符数组
ops
,其中包含字符'nswe',用于表示四个可能的移动方向。 -
声明了一个二维字符数组
g[N][M]
,用于表示迷宫的网格。 -
声明了用于存储关于距离、路径和迷宫状态信息的数组和变量。
-
定义了一个
Point
结构,用于表示人当前的位置和他们面朝的方向。 -
定义了一个
check
函数,用于检查给定的位置是否有效(在界限内且不是障碍物)。 -
定义了一个
bfs_man
函数,执行广度优先搜索,以找到从人当前位置到目标位置的最短路径,同时避免箱子的位置。 -
定义了一个
bfs_box
函数,执行广度优先搜索,以找到箱子到达目标位置的最短路径。它还计算了箱子和人在每一步的距离和路径。 -
main
函数读取输入,并且主要的迷宫求解逻辑位于其中。