HDU 1180 诡异的楼梯
【原题链接】http://acm.hdu.edu.cn/showproblem.php?pid=1180
【关注一下】http://www.seayar.tk
【题意解释】
这道题是中文题,即使是这样,我们也得好好研究下题目的意思。
一、首先是从某一个点去找另一个点;
二、有堵死路的墙,有状态可变化的楼梯;
三、可以在走廊上停留,随便你停多久,但是不能在楼梯上停留;
四、梯子每一分钟变化一次,这也就是说即使遇到了你不能走过去的梯子,在原地等一分钟后也就可以过去了;
五、每次走到相邻的格子而不能斜走,每移动一次恰好为一分钟,并且登上楼梯并经过楼梯到达对面的整个过程只需要一分钟(也就是说如果能通过这里,那么就当没有这个方格就行了);
【背景介绍】
通过上面的题意分析,很明显这是一道搜索题。所以我们很容易想到的就是能否用DFS或者BFS来做。不过学过数据结构的人都知道,一般我们会首选DFS,第一实现比较简单,第二按道理来说在时间复杂度和空间复杂度来说都应该是要比BFS要好的。可是这道题不然,通过我自己的亲身试验(用DFS做),很明显的TLE了(DFS的代码我附在AC代码后面)。后来在网上搜了解题报告,几乎千篇一律,没有任何的解释说明,完全不像是解题报告,因此我自己写点东西,方便自己以后复习。
【算法介绍】
好了,现在我们来说说BFS(宽度优先搜索),BFS和DFS的不同之处在于它一次性将所有的子树结点拓展了(详细请参照算法设计与分析)。先上个图,希望你们能明白一点。
这个图代表什么意思是:左边那棵树是按照DFS来遍历的,而右边这个是按照BFS来遍历的,左边那个实现起来很简单,直接用递归函数的调用,然后就可以依次遍历完所有的结点(注意:此处是针对树的,如果在图中用DFS,要用一个辅助空间来记录访问路径的状态,以后我会写出DFS的算法)。
而右边那个遍历的序列和左边的明显不同,这个遍历的序列方式就是今天的主角BFS,我们发现它是一层一层的拓展下去的,如果是DFS,我们往深处遍历,遇到障碍了可以直接回溯到上一个状态,但是BFS的话,这种方法就失效了。
如何来解决这个问题呢?其实我们有两种方式,下面会一一介绍:
1、 用一个队列,将当前所拓展的结点保存起来。
我们现在就需要用到队列这种数据结构。
1)在一开始的时候,将A放入队列中;
2)当拓展A的时候,我们将A从队列中取出,将依次拓展得到的B、C放入队列,此时队列中的元素成了:B、C;
3)然后再取出B进行拓展,再压入队列中就是C、D、E;
4)再取出C,然后依次这样进行,最后直到整棵树(图)遍历完。这是最简单的一种方法,但是使用的情况是只需要用BFS找出其中一个解就行了。
Tips:其实BFS有时候不能找出所有解,只能通过DFS来找出,但是这道题我们不用担心。
2、 用优先队列,将当前的结点保存起来。
上面说到了一种实现方式,但是很明显,上面那种实现方式虽然能找出一个解,但可能得到的不是我们要找的最优解。我们这样将上一个队列做一下调整,就是将队列中的元素进行排序(按照从大到小或者从小到大),这样我们每一次从队列中取出新元素进行拓展时,得到的始终是当前的最优解,这就是优先队列,按照某种优先关系进行BFS拓展。
煮个栗子:
若A,B,C,D,E,F的优先级分别为1,5,3,6,4,2(大的优先级低),那我们遍历的顺序就变成了下面这个样子:
1)首先放入A,然后取出A进行拓展,按照优先级来排列的话,就是C、B(与上面不同了);
2)再取出C进行拓展,得到F,由于F的优先级高于B,所以仍然放在前面,这时队列顺序为:F、B;
3)取出F拓展,发现没有子结点,所以不用拓展;
4)取出B拓展,得到D、E,由于E的优先级高,所以得到序列:E、D;
以上是用优先队列进行BFS时的遍历情况。通过这个方法,我们可以以最快的方式找到最优的那个解。
但是优先队列如何来构造呢?其实在C语言里面用最大堆和最小堆来解决,或者简单点的就对每一次的序列进行快速排序(时间复杂度比较高了)。但是C++里面为我们提供了这样的数据结构,所以不用我们自己写代码去实现了,但是前提是要用C++。
以上就是我们所要用到的算法了,下面就来看看怎么来解决这道题吧!
Tips:我是比较喜欢C的,但是数据结构学的不是很好(主要是没认真学),所以这里我也不给出C的代码了,有空的时候我会尝试着专门写一个这样的报告。
【题目解答】
既然知道了要使用BFS和优先队列,那我们就有思路了。但是笔者在解题的过程中,WA了无数遍,主要原因是没有很深入的注意细节和BFS的精髓。后来在参考了一位同学的代码后,才艰难的将这道题A了。下面我就来说说要注意的地方。
一、采用的数据结构
我采用的是将每个格子写成一个坐标的方式,然后对应着走到每个坐标用了多少步,所以用了一个结构体,可以参看代码;
同时也要用一个二维数组来储存输入的地图,这个大家都容易想到。
最后是要一个优先队列。
二、优先队列用C++构造的方法:
下面来说下C++是如何构造优先队列的。首先我们要包含头函数<queue>,然后在结构体中加入下面一段代码(主要用来告诉队列如何决定优先级):
friend bool operator < (const point &a,const point &b)
{
return a.step>b.step;
}
这个是C++的用法,我没有深入学习过C++,所以照葫芦画瓢,其实和qsort的比较函数用法比较类似;
然后是如何创建一个队列,要用到下面的代码:
priority_queue <point> q;
这代表创建了一个元素类型是point的优先队列q。大家不理解的可以照葫芦画瓢就行了。
三、BFS搜索时的细节问题
这个细节问题比较重要。由于这里遍历的是图而不是树,所以当我们发现能往前面走的时候我们就应该将后面的路给堵死,不然的话我们按照上下左右四个方向遍历的时候,来的路没堵死然后又走回去了,这是不行的。还有每移动一步,记得步数要加1。
四、遇到桥该怎么办
这是最麻烦的地方,因为这个地方需要注意的东西有很多。下面我一个一个的说。
1、首先是遇到桥了我怎么判断我过得去还是过不去。这个可以通过你现在已经走了几步来确定的,由于初始状态已经告诉你了,那么你每走一步桥就改变一次它的状态,所以你可以通过你目前走了几步来推断当前桥的状态,然后再和你行走的方向进行判断,就可以知道过得去还是过不去;
2、过不去的时候怎么办?这个其实我在解析题目意思的时候就说了,过不去的时候我们就在当前这个格子里边等一分钟呗,这个时候桥就会转换成你能过去的状态了;
3、过桥算出几步呢?题目说了,过桥是一下子就过去了,所以你过桥的时候你不用考虑时间,虽然你走了两个格子,但是时间还是按照1分钟来算;
4、如果过桥之后发现对面是死胡同怎么办?这个是我一直没有注意到的地方,所以WA了无数次。如果你过桥后发现前面不能去了,那你只能又退回来,所以如果你知道那边是堵死的了,你就不用过桥了。但是你过去后你要注意,要把桥封死,也要把你没过桥之前的那个地方也封死,这样你就不会陷入死循环了。
五、达到终点了怎么办?怎么办?说明你已经得到答案了呗,直接输出就行了!
注意了以上几点细节,这道题你基本上已经A了,下面给一些测试样例,帮助WA的老辛苦的人找出错误吧!
Sample Input
5 5
**..T
**.*.
**|..
**.**
S..**
5 5
**..T
**.*.
**-..
**.**
S..**
5 5
.|.-T
-*-*|
.*.|.
-*-**
S|.**
5 5
S....
-|-|-
.....
-|-|-
....T
1 3
S-T
1 3
S|T
1 5
S|.|T
1 5
S-.-T
1 5
S|.-T
1 5
S-.|T
2 5
*.-.T
.S.|.
1 2
ST
2 4
-|.S
|T.-
1 5
S.-.T
3 5
.|*.T
.-.|.
S.-*.
Sample Output
8
7
7
8
1
2
4
3
3
2
3
1
3
4
5
【参考代码】
1 #include <stdio.h> 2 #include <string.h> 3 #include <math.h> 4 #include <stdlib.h> 5 #include <queue> 6 #include <iostream> 7 using namespace std; 8 9 struct point //构造一个坐标和关于步数的优先队列(从小到大),我也不懂,照葫芦画瓢的 10 { 11 int x; 12 int y; 13 int step; 14 friend bool operator < (const point &a,const point &b) 15 { 16 return a.step>b.step; 17 } 18 }; 19 20 char map[30][30]; 21 int dire[4][2]= 22 { 23 {-1,0},{0,1},{1,0},{0,-1} 24 }; //上下左右 25 //int visit[30][30]; 26 int m,n; 27 point start,end; 28 //int min; 29 int result; 30 31 int judge(point *temp,int mark) 32 { 33 34 char newState = map[temp->x][temp->y]; 35 if(temp->step%2==0) //变换样式 36 { 37 switch(newState) 38 { 39 case '|': 40 { 41 newState='-'; 42 break; 43 } 44 case '-': 45 { 46 newState='|'; 47 break; 48 } 49 } 50 } 51 if(((mark==0||mark==2)&&newState=='-')||((mark==1||mark==3)&&newState=='|')) 52 { 53 return 0; 54 } 55 else return 1; 56 } 57 58 int bfs(point a) 59 { 60 priority_queue <point> q; //照葫芦画瓢 61 q.push(a); //将开始的结点先装进去 62 map[a.x][a.y]='*'; //将拓展结点变成死结点 63 //下面开始拓展结点 64 while(!q.empty()) //如果优先队列不空 65 { 66 //取出队列第一个元素拓展 67 point first = q.top(); 68 if(first.x==end.x&&first.y==end.y) //到达终点 69 { 70 printf("%d\n",first.step); 71 return 1; 72 } 73 //以下是继续进行BFS 74 first.step++; //走一步 75 q.pop(); //删除取出的那个坐标 76 //搜索四个方向 77 for(int i=0;i<4;i++) 78 { 79 //下一步的坐标 80 point s_first; 81 s_first.x = first.x+dire[i][0]; 82 s_first.y = first.y+dire[i][1]; 83 s_first.step = first.step; 84 if(map[s_first.x][s_first.y]!='*') //不碰墙 85 { 86 if(map[s_first.x][s_first.y]=='.'||map[s_first.x][s_first.y]=='T') //还是走廊! 87 { 88 //那就走下去呗 89 map[s_first.x][s_first.y]='*'; //走过了,那就堵死吧 90 q.push(s_first); //加入新的拓展结点到优先队列中 91 } 92 else 93 { 94 if(map[s_first.x][s_first.y]=='|'||map[s_first.x][s_first.y]=='-') //遇到楼梯了 95 { 96 if(judge(&s_first,i)) //可以直接通过 97 { 98 s_first.x += dire[i][0]; 99 s_first.y += dire[i][1]; 100 //继续往原来那个方向前移一步 101 if(map[s_first.x][s_first.y]!='*') //要保证桥的那边不是堵死的 102 { 103 if(map[s_first.x][s_first.y]=='.'||map[s_first.x][s_first.y]=='T') //还是走廊! 104 { 105 //那就走下去呗 106 map[s_first.x][s_first.y]='*'; //走过了,那就堵死吧 107 map[first.x+dire[i][0]][first.y+dire[i][1]]='*'; //桥也要堵死 108 q.push(s_first); //加入新的拓展结点到优先队列中 109 } 110 } 111 // q.push(s_first); //加入新的拓展结点到优先队列中 **忘记这个地方多余,老是WA 112 } 113 else 114 { 115 q.push(first); //呆在原地不动吧 116 } 117 } 118 } 119 } 120 } 121 } 122 } 123 124 void Init() 125 { 126 memset(map,0,sizeof(map)); 127 // memset(visit,0,sizeof(visit)); 128 // min = 600000; 129 int i,j; 130 for(i=0; i<=n+1; i++) 131 { 132 map[m+1][i]=map[0][i] = '*'; 133 } 134 for(j=1; j<=m; j++) 135 { 136 map[j][0]=map[j][n+1]='*'; 137 } 138 for(i=1; i<=m; i++) 139 { 140 for(j=1; j<=n; j++) 141 { 142 char temp; 143 scanf("%c",&temp); 144 if(temp=='\n') scanf("%c",&temp); 145 if(temp=='S') 146 { 147 start.x=i; 148 start.y=j; 149 } 150 if(temp=='T') 151 { 152 end.x=i; 153 end.y=j; 154 } 155 map[i][j]=temp; 156 } 157 } 158 start.step=0; //初始化走了几步,即为0 159 } 160 161 int main() 162 { 163 #ifndef ONLINE_JUDGE 164 freopen("G:\\Documents and Settings\\Zhu\\桌面\\input.txt","r",stdin); 165 #endif 166 167 while(scanf("%d %d",&m,&n)!=EOF) 168 { 169 Init(); //初始化地图边框 170 bfs(start); 171 } 172 return 0; 173 } 174 175 //失败的DFS,实践证明会TLE,所以不得不转换为BFS加优先队列 176 // void dfs(int x,int y,int deep) 177 // { 178 // if(map[x][y]=='*') return; 179 // if(x==end.x&&y==end.y) 180 // { 181 // if(deep<min) min=deep; 182 // return; 183 // } 184 // else 185 // { 186 // int sx=x,sy=y,sdeep=deep; 187 // sx--; 188 // judge(&sx,&sy,&sdeep,0); 189 // if(visit[sx][sy]==0) 190 // { 191 // visit[sx][sy]=1; 192 // dfs(sx,sy,sdeep+1); 193 // visit[sx][sy]=0; 194 // } 195 // sx=x; 196 // sy=y; 197 // sy++; 198 // judge(&sx,&sy,&sdeep,1); 199 // if(visit[sx][sy]==0) 200 // { 201 // visit[sx][sy]=1; 202 // dfs(sx,sy,sdeep+1); 203 // visit[sx][sy]=0; 204 // } 205 // sx=x; 206 // sy=y; 207 // sx++; 208 // judge(&sx,&sy,&sdeep,2); 209 // if(visit[sx][sy]==0) 210 // { 211 // visit[sx][sy]=1; 212 // dfs(sx,sy,sdeep+1); 213 // visit[sx][sy]=0; 214 // } 215 // sx=x; 216 // sy=y; 217 // sy--; 218 // judge(&sx,&sy,&sdeep,3); 219 // if(visit[sx][sy]==0) 220 // { 221 // visit[sx][sy]=1; 222 // dfs(sx,sy,sdeep+1); 223 // visit[sx][sy]=0; 224 // } 225 // } 226 // return; 227 // }