不错的搜索题 ^^
分析:需要判断人和箱子的相对位置,两个bfs嵌套,挪动一次箱子就判断一次人是否能到达使箱子挪动的地方。两次bfs,因为数据量不大,所以应该可以过
很棒的搜索题
需要注意的地方:
1.人不能穿过箱子,所以每次在处理人的行进路径时需要把地图G中箱子处转换成1
2.箱子不一定沿着到达目标点的最短路径前进
3.需要开4维数组记录人和箱子的相对状态state,因为人在箱子4个方向是不同的状态会产生不同的后继状态,因此需要标记
代码:
/*
Note:
一开始想把问题分解成两个子问题
1.用bfs找到最短路记录下路径 2.dfs判断人能否到达路径中每个点的前一个状态(即合法推箱子的状态)
后来发现有严重问题
例如:
1
7 6
4 1 1 1 1 1
0 1 0 0 0 1
0 1 0 1 0 1
0 2 0 1 0 3
0 0 0 1 0 1
0 0 0 0 0 1
0 0 0 0 0 1
我的思路只能选择两条最优路径中一条,然后进行判断,如果选择上面一条,那肯定是不对的,最合理的方法是选择下面的路径
所以这个思路是必错无疑
挪动一次箱子就判断一次人是否能到达使箱子挪动的地方
两次bfs,因为数据量不大,所以应该可以过
*/
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
//#define test
int n,m;
int G[10][10];
int vis[10][10];
int state[9][9][9][9]; // 记录箱子和人的相对状态
int box_x,box_y,per_x,per_y,x_e,y_e; // box person X 的位置
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
struct point{
int x,y;
};
struct node{ // 存放现在的步数step 方向r box & per 的位置
int r,step;
struct point box;
struct point per;
};
bool check_per(int x,int y){ // 判断person的行走是否合法
if(x<0 || x>=n || y<0 || y>=m || vis[x][y] ) return false;
return true;
}
bool judge_per(int x_s,int y_s,int x_e,int y_e){ // person能否从(x_s,y_s)到达(x_e,y_e)
queue<point>q;
point cur,next;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
vis[i][j]=G[i][j];
cur.x=x_s; cur.y=y_s;
vis[cur.x][cur.y]=1;
q.push(cur);
while(!q.empty()){
cur=q.front();
q.pop();
if(cur.x==x_e && cur.y==y_e) return true;
for(int i=0;i<4;i++){
next.x=cur.x+dx[i];
next.y=cur.y+dy[i];
if(check_per(next.x,next.y)){
vis[next.x][next.y]=1;
q.push(next);
}
}
}
return false;
}
bool check_box(int x,int y){
if(x<0 || x>=n || y<0 || y>=m || G[x][y]) return false;
return true;
}
int judge_box(){
queue<node> q;
node cur,next;
cur.box.x=box_x; cur.box.y=box_y;
cur.per.x=per_x; cur.per.y=per_y;
cur.step=0;
for(int i=0;i<4;i++) // 找到开始时人和箱子的相对方向r
if(cur.box.x-cur.per.x==dx[i] && cur.box.y-cur.per.y==dy[i]){
cur.r=i; break;
}
state[cur.per.x][cur.per.y][cur.box.x][cur.box.y]=1;
q.push(cur);
while(!q.empty()){
cur=q.front(); q.pop();
if(cur.box.x==x_e && cur.box.y==y_e) return cur.step;
for(int i=0;i<4;i++){
bool ok=false;
if(cur.r==i) ok=true; // 与人和箱子的相对方向相同时,开始推箱子
else{ // 换个方向尝试推箱子
G[cur.box.x][cur.box.y]=1;
ok=judge_per(cur.per.x,cur.per.y, cur.box.x-dx[i],cur.box.y-dy[i]);
G[cur.box.x][cur.box.y]=0;
}
if(ok){
next.box.x=cur.box.x+dx[i]; next.box.y=cur.box.y+dy[i];
if( check_box(next.box.x,next.box.y) ){ // 检查箱子的移动是否合法
next.per.x=cur.box.x; next.per.y=cur.box.y;
if(!state[next.per.x][next.per.y][next.box.x][next.box.y]){
state[next.per.x][next.per.y][next.box.x][next.box.y]=1;
next.r=i; // 记录下人和箱子的相对方向
next.step=cur.step+1;
q.push(next);
}
}
}
}
}
return -1;
}
int main(){
#ifdef test
freopen("Hdu 1254 推箱子 test case.txt","r",stdin);
#endif
int T;
int aim_x,aim_y;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
for(int j=0;j<m;j++){
scanf("%d",&G[i][j]);
if(G[i][j]==2) {box_x=i;box_y=j; G[i][j]=0;}
else if(G[i][j]==3) {x_e=i ;y_e=j; G[i][j]=0;}
else if(G[i][j]==4) {per_x=i;per_y=j; G[i][j]=0;}
}
// 一开始时人就无法到达箱子的周围的某一个格子,这时就不需要搜索了,直接-1
bool ok=false;
for(int i=0;i<4;i++){ <span style="white-space:pre"> </span> // 判断开始时是否在箱子周围
if(box_x-per_x==dx[i] && box_y-per_y==dy[i]) {
ok=true; break;
}
}
if(!ok){ // 判断能否到达箱子周围
G[box_x][box_y]=1; <span style="white-space:pre"> </span> // 人无法穿过箱子**
for(int i=0;i<4;i++){
aim_x=box_x+dx[i]; aim_y=box_y+dy[i];
ok=judge_per(per_x,per_y,aim_x,aim_y);
if(ok){ per_x=aim_x; per_y=aim_y; break; } // 能到达箱子周围让person的位置更新一下即可
}
G[box_x][box_y]=0;
}
if(!ok) {printf("-1\n");continue;}
memset(state,0,sizeof(state));
printf("%d\n",judge_box());
}
return 0;
}