重要的事情说10遍!?????????
当你样例全过、自编样例全过,怎么怎么觉得都对,但就是WA的时候,请在提交代码时,换一下编译器,可能就A了。
比如这样子:(手动微笑)
交了十几遍WA,心态都炸了!!!!我真的难受啊!!!!!!!!
————————————————我是分割线————————————————
好了好了,言归正传,开始这道比较水的题目的题解。
题目大意
给一个迷宫,S是入口,E是出口,#是墙,.是空地
分别输出左优先步数、右优先步数、最短路步数
左优先就是走到一个位置,按照左、上、右、下的顺序进行移动,右优先则是“右上左下”
其中,题目中还给定了一些限制:
①只有一个入口和一个出口,且规定从入口出发,一定能找到出口。
②为了确保左优先和右优先一定能找到出口,规定入口和出口都在迷宫的四周且不在转角处。(都在边界对于初始化的面向的确定很有帮助,后文会提到。)
③S和E至少要相隔一堵墙。(其实没啥用)
小贴士:
说它水,是因为原理很基础,做题思路很快就能出来。DFS模拟面向和转向,BFS直接求最短路就OK了。
但是,在模拟面向和转向的时候,需要去思考,这篇博客贴的代码是“真·模拟”,我写完之后,看了很多博主的优秀博客,发现他们的代码很简单,因为同样是在模拟面向和转向的时候,他们进行了思考,找到了小规律、小技巧,从而将代码长度缩短,简便程度提高。
不是马后炮,我觉得如果我看完题目,拿着笔动动手去画一画的话,找到这个规律不难。因为我就是凭空去想,只能想到比较裸的“真·模拟”,前期思维过程少,意味着你的代码可能要长很多很多。所以经过这道题,我觉得还是要勤动手,不要懒,如果想到的方法感觉上实现有些麻烦的话,要去寻找一些小规律,动手+动脑是最高效的方法。
————————————————我是分割线————————————————
首先贴一下比较简单的做法的博文吧。
POJ3083-Children of the Candy Corn
接下来就是“真·模拟”的方法。
每进入一次DFS,需要进行两步操作:按照当前面向进行移动、改变面向。
以左优先代码为例:
void dfs_l(int x,int y,int dir)
{
///当前操作
num[0]++;
///结束条件
if(maze[x][y]=='E'){
success=true;
return;
}
for(int i=0;i<4;i++){
///进行移动
int nx=x+step_l[dir][i][0];
int ny=y+step_l[dir][i][1];
///深搜条件
if(nx>=0&&nx<h&&ny>=0&&ny<w&&!success&&maze[nx][ny]!='#'){
dfs_l(nx,ny,change_l[dir][i]);
}
}
}
DFS还是按照四步走的原则:当前操作、结束条件、递归条件、是否回溯
①当前DFS深度需要进行计数操作
②结束条件是“找到出口”
③for循环四个方向,按照当前面向移动、递归条件
④递归时,改变面向并传入下一层DFS
⑤这道题不需要回溯,因为是按照左优先的方式,一直递归下去,如果走到死胡同,需要往回走,但是不需要重新计数,而是在原来的基础上继续计算需要走的步数。故而不需要对已经访问的点进行标记、解除标记。
然后介绍移动数组和改变面向数组,还是以左优先为例。
1、移动数组
int step_l[4][4][2]={
///x代表四种面向不同的走法,y代表每种面向的按顺序的四种不同走法,z表示横纵坐标变化
{{0,-1},{-1,0},{0,1},{1,0}},///上
{{0,1},{1,0},{0,-1},{-1,0}},///下
{{1,0},{0,-1},{-1,0},{0,1}},///左
{{-1,0},{0,1},{1,0},{0,-1}}///右
};
是的,你没看错,我用了一个三维数组来模拟移动数组。
第一维代表,当前面向——0上、1下、2左、3右
第二维代表,当前面向下,按优先级排列的四种走法
第三维代表,横纵坐标的变化
故而你看到的移动代码是这样的:
int nx=x+step_l[dir][i][0];
int ny=y+step_l[dir][i][1];
dir代表当前面向,i代表第几种走法,01对应xy。也对应了上述三维数组的定义,模拟了移动。
听上去很麻烦,但是并不难想到,事实上也不难实现。
对比右优先的移动数组,你可以发现,就在左优先的基础上,把四种走法的第1、3号进行了交换而已。即,左右优先进行了交换,因为左优先是“左上右下”的顺序,而右优先是“右上左下”的顺序。
int step_r[4][4][2]={
{{0,1},{-1,0},{0,-1},{1,0}},///上
{{0,-1},{1,0},{0,1},{-1,0}},///下
{{-1,0},{0,-1},{1,0},{0,1}},///左
{{1,0},{0,1},{-1,0},{0,-1}}///右
};
2、改变面向数组
///change数组代表不同面向时,四种走法后,面向的改变
int change_l[4][4]={{2,0,3,1},{3,1,2,0},{1,2,0,3},{0,3,1,2}};
int change_r[4][4]={{3,0,2,1},{2,1,3,0},{0,2,1,3},{1,3,0,2}};
又是一个二维数组。
第一维代表,当前面向——0上、1下、2左、3右
第二维代表,当前面向下,按优先级排列的四种走法对应的,要改变成的面向
故而你看到的传参是这样的:
dfs_l(nx,ny,change_l[dir][i]);
dir代表当前面向,i代表第几种走法,对应了上述二维数组的定义,模拟了改变面向。
3、BFS求最短路
然后是求最短路的BFS,很裸,也不用注意什么,直接敲就行了。
这里用到的_move数组就是普通的移动数组,不需要考虑面向,四个方向什么顺序都OK。
有关BFS的细节就不多讲了,不会的可以参考一些优秀的BFS的讲解博客。
struct point{
int x,y;
point(int x=0,int y=0):x(x),y(y){}
};
bool vis[maxn][maxn];
int ans[maxn][maxn];
int _move[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
int bfs(int x,int y)
{
queue<point> q;
q.push(point(x,y));
vis[x][y]=true;
while(!q.empty()){
point v=q.front();
q.pop();
for(int i=0;i<4;i++){
int nx=v.x+_move[i][0];
int ny=v.y+_move[i][1];
if(nx>=0&&nx<h&&ny>=0&&ny<w&&maze[nx][ny]!='#'&&!vis[nx][ny]){
vis[nx][ny]=true;
q.push(point(nx,ny));
ans[nx][ny]=ans[v.x][v.y]+1;
if(maze[nx][ny]=='E') return ans[nx][ny];
}
}
}
}
4、主函数
再就是主函数里DFS第一层传入的面向参数了,由于入口在边界,所以面向很容易确定下来,只需要根据它的行数或列数即可确定。0上、1下、2左、3右。
if(sx==h-1)
init_dir=0;
else if(sx==0)
init_dir=1;
else if(sy==w-1)
init_dir=2;
else if(sy==0)
init_dir=3;
还要注意一点,输入是先w后h,即先列后行,不要搞错了。
给俩样例自己测试吧~
5
5 5
##E##
#…#
#…##
#…#
###S#
6 8
####S#
#…#.#
##.#.#
#…#
##…#
E…#.#
#…#
AC代码(要用C++交!要用C++交!要用C++交!要用C++交!要用C++交!)
#include<stdio.h>
#include<queue>
#define maxn 50
using namespace std;
int w,h;
bool success;
char maze[maxn][maxn];
int sx,sy;
int p,q;
int num[3];///分别代表三种走法的步数
int step_l[4][4][2]={
///x代表四种面向不同的走法,y代表每种面向的按顺序的四种不同走法,z表示横纵坐标变化
{{0,-1},{-1,0},{0,1},{1,0}},///上
{{0,1},{1,0},{0,-1},{-1,0}},///下
{{1,0},{0,-1},{-1,0},{0,1}},///左
{{-1,0},{0,1},{1,0},{0,-1}}///右
};
int step_r[4][4][2]={
{{0,1},{-1,0},{0,-1},{1,0}},///上
{{0,-1},{1,0},{0,1},{-1,0}},///下
{{-1,0},{0,-1},{1,0},{0,1}},///左
{{1,0},{0,1},{-1,0},{0,-1}}///右
};
///change数组代表不同面向时,四种走法后,面向的改变
int change_l[4][4]={{2,0,3,1},{3,1,2,0},{1,2,0,3},{0,3,1,2}};
int change_r[4][4]={{3,0,2,1},{2,1,3,0},{0,2,1,3},{1,3,0,2}};
void dfs_l(int x,int y,int dir)
{
///当前操作
num[0]++;
///结束条件
if(maze[x][y]=='E'){
success=true;
return;
}
for(int i=0;i<4;i++){
///进行移动
int nx=x+step_l[dir][i][0];
int ny=y+step_l[dir][i][1];
///深搜条件
if(nx>=0&&nx<h&&ny>=0&&ny<w&&!success&&maze[nx][ny]!='#'){
dfs_l(nx,ny,change_l[dir][i]);
}
}
}
void dfs_r(int x,int y,int dir)
{
///当前操作
num[1]++;
///结束条件
if(maze[x][y]=='E'){
success=true;
return;
}
for(int i=0;i<4;i++){
///进行移动
int nx=x+step_r[dir][i][0];
int ny=y+step_r[dir][i][1];
///深搜条件
if(nx>=0&&nx<h&&ny>=0&&ny<w&&!success&&maze[nx][ny]!='#'){
dfs_r(nx,ny,change_r[dir][i]);
}
}
}
///以上是dfs,以下是bfs
struct point{
int x,y;
point(int x=0,int y=0):x(x),y(y){}
};
bool vis[maxn][maxn];
int ans[maxn][maxn];
int _move[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
int bfs(int x,int y)
{
queue<point> q;
q.push(point(x,y));
vis[x][y]=true;
while(!q.empty()){
point v=q.front();
q.pop();
for(int i=0;i<4;i++){
int nx=v.x+_move[i][0];
int ny=v.y+_move[i][1];
if(nx>=0&&nx<h&&ny>=0&&ny<w&&maze[nx][ny]!='#'&&!vis[nx][ny]){
vis[nx][ny]=true;
q.push(point(nx,ny));
ans[nx][ny]=ans[v.x][v.y]+1;
if(maze[nx][ny]=='E') return ans[nx][ny];
}
}
}
}
int main()
{
int n,init_dir;
scanf("%d",&n);
while(n--)
{
scanf("%d%d",&w,&h);
for(int i=0;i<h;i++)
scanf("%s",maze[i]);
for(int i=0;i<h;i++)
for(int j=0;j<w;j++){
if(maze[i][j]=='S')
sx=i,sy=j;
vis[i][j]=false;
ans[i][j]=0;
}
ans[sx][sy]=1;
for(int i=0;i<3;i++)
num[i]=0;
///以上为存图与初始化
///判断初始方向
if(sx==h-1)
init_dir=0;
else if(sx==0)
init_dir=1;
else if(sy==w-1)
init_dir=2;
else if(sy==0)
init_dir=3;
success=false;
dfs_l(sx,sy,init_dir);
printf("%d",num[0]);
success=false;
dfs_r(sx,sy,init_dir);
printf(" %d",num[1]);
num[2]=bfs(sx,sy);
printf(" %d\n",num[2]);
}
return 0;
}