题解 UVA589 【Pushing Boxes】

UVA589 Pushing Boxes

为什么题解里都是建图或者奇妙的双 BFS ?明明一个 BFS 就可以解决了嘛。

注意题意:

  • 把推的数量减到最少(一般的 BFS 中距离是第一关键字,但是本题中推的数量才是第一关键字)。

  • 如果有不止一个这样的序列,那么选择一个最小化总移动次数(步行和推送)的序列(也就是说,在记录推的次数的同时也要记录走的步数)。

  • 在每个测试用例之后输出一个空行(意思就是, 每一组输入和输出后面要有额外的一个空行)

  • \(Maze\) #+ 数字 也是输出的一部分。

  • 每组数据处理之前记得对标记数组进行清空。

进入正题

一个 BFS 解决本题的关键在于使用堆(优先队列),将推的次数作为第一关键字。

(当推的次数相同时,应当根据走的次数进行排序)

部分代码如下:

struct sta {//state:状态 
	int x,y;//人的位置 
	int bx,by;//箱子的位置(b:box) 
	string s="";/*用于记录走到当前位置的走法
	(这样写比较浪费空间,但是可以过,毕竟数据范围只
  	有20*20) 
	 */ 
	int ps,step;//ps:push用于记录推箱子的次数
	//step 记录走的总次数 
	bool operator <(const sta &a)const {
		if(ps==a.ps)return step>a.step;
		return ps>a.ps;
	//由于直接用大根堆写起来比较短,所以直接重载<
	//注意:如果要使用小根堆,必须将 > 和 < 都重载一遍 
	}
} s;//s记录初始状态 

采用四维数组 \(vis\) 来记录是否走过

( BFS 板子:)

void bfs(sta s,int num) {
	priority_queue<sta> q;
	q.push(s);
	vis[s.x][s.y][s.bx][s.by]=1;
	while(!q.empty()) {
		sta t=q.top();
		q.pop();
		for(int i=0; i<4; i++) {
			sta P=t;
			P.x=t.x+g[i][0],P.y=t.y+g[i][1];
			if(check(P,i)) {
				if(pc[P.bx][P.by]=='T') {
					cout<<"Maze #"<<num<<endl;
					cout<<P.s<<endl;
					f=0;//此处f用于标记能否在走
   					到,f=0表示走到了,f=1表走不到 
					return ;
				}
				q.push(P);
			}
		}
	}
}
    

难点:

(好吧其实只要小心一点写起来并不难)

可以看出来,代码的成败在于上面 BFS 板子里的 check 函数。

(check 函数用于判断能不能走,这里采用了直接在 check 函数里 面对参数进行修改的技巧,可以使 BFS 显得干净清楚。推荐大家多写函数,可以增强代码的可读性,易于调试)

  • 首先是边界的判断,不需要多讲。

  • 第二,当人往前走到空地上时,直接将走的步数加 \(1\) , 将走法和当前状态标记一下,比较简单。

  • 第三,当遇到箱子时,应当先“尝试”将箱子往同方向移动,进行边界判断,如果能推,就将相关参数进行修改。

code:

bool check(sta &t,int i) {
	//t是需要判断能否走的状态,i是人走的方向 
	//用mk 代替了vis[t.x][t.y][t.bx][t.by],写起来方便点 
	if(t.x<1||t.x>n||t.y<1||t.y>m||pc[t.x][t.y]=='#'||mk)return false;//边界判定 
	if(t.x==t.bx&&t.y==t.by) {//遇到箱子 
		int xx=t.bx+g[i][0],yy=t.by+g[i][1];
		//箱子走一步
		//事先定义了g数组,用于向四个方向走 
	if(pc[xx[yy]=='#'||xx<1||xx>n||yy<1||yy>m)return false;
		//箱子的边界判定 
		t.bx=xx,t.by=yy;
		t.ps++;
		t.s+=c[i];//记录走法 
	} else t.s+=c[i]-'A'+'a';
	t.step++;
	mk=1;//对新状态进行标记 
	return true;
}

其实,只要搞清楚了题目中各种关系,这道题目并不难(暴搜就行)。

完整代码如下:

#include<bits/stdc++.h>
#define mk vis[t.x][t.y][t.bx][t.by]
using namespace std;
const int N=21;
int n,m,g[4][2] {0,1,0,-1,1,0,-1,0};
bool vis[N][N][N][N],f;
char pc[N][N],c[4] {'E','W','S','N'};
struct sta {//state:状态
	int x,y;//人的位置
	int bx,by;//箱子的位置(b:box)
	string s="";/*用于记录走到当前位置的走法
	(这样写比较浪费空间,但是可以过,毕竟数据范围只有20*20)
	 */
	int ps,step;//ps:push用于记录推箱子的次数
	//step 记录走的总次数
	bool operator <(const sta &a)const {
		if(ps==a.ps)return step>a.step;
		return ps>a.ps;
		//由于直接用大根堆写起来比较短,所以直接重载 <
		//注意:如果要使用小根堆,必须将 > 和 < 都重载一遍
	}
} s;//s记录初始状态
void input() {
	//读入 
	for(int i=1; i<=n; i++) {
		for(int j=1; j<=m; j++) {
			cin>>pc[i][j];
			if(pc[i][j]=='S')s.x=i,s.y=j;
			if(pc[i][j]=='B')s.bx=i,s.by=j;
		}
	}
}
bool check(sta &t,int i) {
	//t是需要判断能否走的状态,i是人走的方向 
	//用mk 代替了vis[t.x][t.y][t.bx][t.by],写起来方便点 
	if(t.x<1||t.x>n||t.y<1||t.y>m||pc[t.x][t.y]=='#'||mk)return false;//边界判定 
	if(t.x==t.bx&&t.y==t.by) {//遇到箱子 
		int xx=t.bx+g[i][0],yy=t.by+g[i][1];
		//箱子走一步
		//事先定义了g数组,用于向四个方向走 
		if(pc[xx][yy]=='#'||xx<1||xx>n||yy<1||yy>m)return false;
		//箱子的边界判定 
		t.bx=xx,t.by=yy;
		t.ps++;
		t.s+=c[i];//记录走法 
	} else t.s+=c[i]-'A'+'a';
	t.step++;
	mk=1;//对新状态进行标记 
	return true;
}
void bfs(sta s,int num) {
	priority_queue<sta> q;
	q.push(s);
	vis[s.x][s.y][s.bx][s.by]=1;
	while(!q.empty()) {
		sta t=q.top();
		q.pop();
		for(int i=0; i<4; i++) {
			sta P=t;
			P.x=t.x+g[i][0],P.y=t.y+g[i][1];
			if(check(P,i)) {
				if(pc[P.bx][P.by]=='T') {
					cout<<"Maze #"<<num<<endl;
					cout<<P.s<<endl;
					f=0;//此处f用于标记能否在走到,f=0表示走到了,f=1表示走不到 
					return ;
				}
				q.push(P);
			}
		}
	}
}
int main() {
	int num=1;//记录测试组数 
	while(scanf("%d%d",&n,&m)&&n&&m) {
		f=1;//前面说过,标记是否能 走到 
		memset(vis,0,sizeof(vis));
		//对标记和各种参数进行清空,以免产生影响 
		s.s="";
		s.ps=0;
		s.step=0;
		input();
		bfs(s,num);
		if(f)cout<<"Maze #"<<num<<endl<<"Impossible."<<endl;
		cout<<endl;//记得换行,否则UVA会判WA 
		num++;
	}
	return 0;
}

(不加注释的话大约 1600 B )AC

(本蒟首 A 的紫题,写本蒟的第一篇题解纪念下)

感谢观看

$ update 2021.1.31$ 修正了关于“分块”的不严谨说法(防止被误解为算法:分块)。

posted @ 2021-06-23 13:52  Maplisky  阅读(92)  评论(0编辑  收藏  举报