题解 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$ 修正了关于“分块”的不严谨说法(防止被误解为算法:分块)。
本文来自博客园,作者:Maplisky,转载请注明原文链接:https://www.cnblogs.com/lbh2021/p/14922363.html