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 // }

 

转载于:https://www.cnblogs.com/seayar/archive/2013/05/15/seayar.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值