UVa1601 - The Morning after Halloween(单向+双向BFS)

来源:互联网 发布:淘宝不能买彩票了吗 编辑:程序博客网 时间:2024/04/29 19:44

给出一个最大为16×16的迷宫图和至多3个ghost的起始位置和目标位置,求最少经过几轮移动可以使三个ghost都到达目标位置。每轮移动中,每个ghost可以走一步,也可以原地不动,需要注意的是任意两个ghost不能在相同的位置,因此也不能出现任意两个ghost对穿,也就是原来是ab,移动之后是ba。每个迷宫图'#'表示墙,' '表示空地,小写字母表示ghost的起始位置,大写字母表示对应ghost的目标位置,比如'a'应该走到'A'。保证任意2×2的空间内都有一个'#'。

  看起来就像是人数多了点的走迷宫嘛,BFS就是了。如果这么做的话果断就会超时,因为在每一个状态可以走的路太多,3个ghost的话,每个有5个方向可走,3个加起来除去原地不动还有124种走法,而且算出来的最少步数也不一定少,第三组样例的步数就多达77步,空间开不下,时间花得多。所以这道题就一定得优化了。

  首先是尽量避免搜索不合法的走法。这个时候就是题里的一个细节,保证任意2×2空间内都有一个‘#’,也就是说,能走的124步里面有相当多的情况是不合法的。每次都压到队列里面然后不合法再排除就浪费了很多时间,所以这就是优化的入口。这里用的办法是把迷宫图给转换成了图,用邻接表保存起来,这样搜索的时候只走可以走的点,省去了走‘#’再排除的时间。

  其次,在判重上也可以提高效率。一开始的时候我用了结构体储存ghost的位置,还动态调整ghost的数量,然后想办法用哈希判重,结果搞得效率奇慢无比样例都跑不出来。实际上还是根据任意2×2都有'#'这个细节,可以粗略的估计出整个迷宫中可以走的空地不超过200个,3个ghost的话建一个三维数组,200×200×200=8000000,完全开得下。另外考虑ghost数量不同的问题,这里想到的方法是把不存在的多余的ghost放到一个孤立的点中,然后使其起始位置和目标位置相同即可,这样就避免了需要根据情况动态调整的麻烦。

  靠着上面两条,已经完全可以A过这题了。

  嗯,那么思路就是获得“输入→建图→BFS”了。想法是很简单,写起来代码能力差真是个问题,各种卡各种崩各种不对,唉……

  其实这只是部分的优化而已,如果还要提高效率的话,就需要用到双向BFS了。不过考虑到本渣的水平……先从单向的过度吧,接下来开始改双向。

#include <cstdio>#include <cstring>#include <cctype>#include <list>#include <algorithm>using namespace std;int w, h, n;char pic[20][20]; // 输入int num[20][20]; // 输入中的位置→图中节点的编号int vis[200][200][200]; // 标记数组int connect[200][200]; // 邻接表int all; // 图中节点的数量int que[10000000][4]; // BFS队列int goal[4]; // 目标状态inline void BFS() {    // 初始化    memset(vis, 0, sizeof(vis));    int fro = 0, rear = 1;    vis[que[0][1]][que[0][2]][que[0][3]] = true;    while(fro < rear) {        int &step = que[fro][0], &a = que[fro][1], &b = que[fro][2], &c = que[fro][3];        if(a == goal[1] && b == goal[2] && c == goal[3]) { goal[0] = step; return; }        for(int i = 0, t1; i <= connect[a][0]; ++i) {            t1 = (i == 0 ? a : connect[a][i]);            for(int j = 0, t2; j <= connect[b][0]; ++j) {                t2 = (j == 0 ? b : connect[b][j]);                for(int k = 0, t3; k <= connect[c][0]; ++k) {                    t3 = (k == 0 ? c : connect[c][k]);                    // 判断冲突-----                    if((t1 && t2 && t1 == t2) || (t1 && t3 && t1 == t3) || (t2 && t3 && t2 == t3)) continue; // 不能重合                    if(t1 && t2 && t1 == b && t2 == a) continue; // t1,t2不能对穿                    if(t1 && t3 && t1 == c && t3 == a) continue; // t1,t3不能对穿                    if(t2 && t3 && t2 == c && t3 == b) continue; // t2,t3不能对穿                    // ----------                    if(!vis[t1][t2][t3]) {                        vis[t1][t2][t3] = 1;                        que[rear][0] = step + 1, que[rear][1] = t1, que[rear][2] = t2, que[rear][3] = t3;                        ++rear;                    }                }            }        }        ++fro;    }}int main() {    int _t = 0;    while(scanf("%d%d%d", &w, &h, &n) && w && h && n) {    // 读取输入-----        gets(pic[0]);        for(int i = 0; i != h; ++i) gets(pic[i]);    // ----------    // 根据输入建立图-----        // 初始化        memset(connect, 0, sizeof(connect));        all = 0;        // 获得编号        for(int i = 0; i != h; ++i) for(int j = 0; j != w; ++j) {            if(pic[i][j] != '#') num[i][j] = ++all;            else num[i][j] = 0;        }        // 建立图        for(int i = 0; i != h; ++i) for(int j = 0; j != w; ++j) if(num[i][j]) {            int &pos = num[i][j];            if(num[i + 1][j]) connect[pos][++connect[pos][0]] = num[i + 1][j];            if(num[i - 1][j]) connect[pos][++connect[pos][0]] = num[i - 1][j];            if(num[i][j + 1]) connect[pos][++connect[pos][0]] = num[i][j + 1];            if(num[i][j - 1]) connect[pos][++connect[pos][0]] = num[i][j - 1];        }    // ----------    // 寻找初始状态和目标状态(测了一下字母范围只在abc之间所以偷懒就这么写了)        //初始化        que[0][0] = que[0][1] = que[0][2] = que[0][3] = 0;        goal[0] = goal[1] = goal[2] = goal[3] = 0;        // 寻找初始状态        for(int i = 0; i != h; ++i) for(int j = 0; j != w; ++j) if(islower(pic[i][j])) {            if(pic[i][j] == 'a') que[0][1] = num[i][j];            if(pic[i][j] == 'b') que[0][2] = num[i][j];            if(pic[i][j] == 'c') que[0][3] = num[i][j];        }        // 寻找目标状态        for(int i = 0; i != h; ++i) for(int j = 0; j != w; ++j) if(isupper(pic[i][j])) {            if(pic[i][j] == 'A') goal[1] = num[i][j];            if(pic[i][j] == 'B') goal[2] = num[i][j];            if(pic[i][j] == 'C') goal[3] = num[i][j];        }    // ----------        BFS();        printf("%d\n", goal[0]);    }}

那什么是双向BFS呢?就是从两个方向进行搜索嘛,一边从起点开始向终点搜索,一边从终点倒着向前搜索,然后从两头往中间接。在算法中的实现方法是正反两方向的搜索交替进行,当搜索出相同的状态时,路就打通了,步数就是两边BFS步数的和。

  双向BFS的好处呢,就是避免了单向BFS步数太多产生的组合性爆炸的情况,就是可能性太多,路又长,到后面分叉越来越多,“爆炸”了,而双向搜索则能在一定程度上延缓组合型爆炸,也就大大提高了效率。当然,就算双向,如果算法写得很烂,也就没救了……所以之前这题卡到死啊……

  不过在写的时候需要注意双向BFS是怎么个双向法。有些同志误以为双向BFS是交替节点搜索,也就是正着搜一个点,然后倒着搜一个点,搜到相同的点就打通路了。但是这样在某些情况下是会出错的。例如下面的这个图(渣鼠标绘制,求不喷……):

  上面是一个环,用双向BFS搜如果是交替搜节点的话就可能出错。假如向队列里面添加节点的是先加最小的话,先从起点开始,队列里面是1,,4,然后从终点搜,队列是3,5。然后再正着搜,该搜1了,搜到了5,然后倒着搜,从3搜出了5,是已经搜到的状态了,然后得出最短路长度是4。然而很显然,正确答案应当是起点→4→5→终点,长度是3。

  那怎么办呢?正确的方法是,一层一层的来。这样,从起点搜到了1和4,终点搜到了3和5,然后从1和4搜到了2和5,这样就和5接上了。

  现在知道双向BFS是啥和怎么双向搜了,接下来把上次单向的版本改成双向的就成了。

  关于怎么实现一层一层的搜,我在这里的做法是记步数,也就是这是搜的第几层,在搜索到下一层的节点之前继续本方向的BFS,当搜到了下一层的节点,就终止搜索。不知道这个方法够不够好,欢迎大神指正……

话说这题POJ上也有,我改成了双向BFS结果MLE了,看来还得把队列改成STL的队列才行啊……不过好在UVA直接用数组就A了。

  从单向BFS改成双向BFS,时间从3.6秒降到2.4秒

#include<cstdio>#include<cstring>#include<cctype>#include<list>#include<algorithm>using namespace std;int w,h,n;char pic[20][20];///输入int num[20][20];///输入中的位置->图中节点的编号int connect[200][200];///邻接表int all;///图中节点的数量int start[4];///起始状态int goal[4];///目标状态int que_fro[1000000][4], que_back[1000000][4];///BFS队列int vis_fro[200][200][200], vis_back[200][200][200];///标记数组bool bfs_fro(const int &_step, int &fro, int &rear){    while(fro < rear){        int &step = que_fro[fro][0],&a =que_fro[fro][1], &b=que_fro[fro][2],&c=que_fro[fro][3];        if(step>_step) break;        if(vis_back[a][b][c]) return true;        for(int i=0,t1;i<=connect[a][0];++i){            t1=(i==0?a:connect[a][i]);            for(int j=0,t2; j<=connect[b][0];++j){                t2=(j==0?b:connect[b][j]);                for(int k=0,t3;k<=connect[c][0];k++){                    t3=(k==0?c:connect[c][k]);                    //判断冲突----                    if((t1&&t2&&t1==t2)||(t1&&t3&&t1==t3)||(t2&&t3&&t2==t3)) continue;///不能对穿                    if((t1&&t2&&t1==b&&t2==a)) continue;///t1,t2不能对穿                    if((t1&&t3&&t1==c&&t3==a)) continue;///t1,t3不能对穿                    if((t2&&t3&&t2==c&&t3==b)) continue;///t2,t3不能对穿                    ///------------                    if(!vis_fro[t1][t2][t3]){                        vis_fro[t1][t2][t3]=1;                        que_fro[rear][0]=step+1,que_fro[rear][1]=t1,que_fro[rear][2]=t2,que_fro[rear][3]=t3;                        ++rear;                    }                }            }        }        ++fro;    }    return false;}bool bfs_back(const int &_step, int &fro, int &rear){    while(fro < rear){        int &step = que_back[fro][0],&a =que_back[fro][1], &b=que_back[fro][2],&c=que_back[fro][3];        if(step>_step) break;        if(vis_fro[a][b][c]) return true;        for(int i=0,t1;i<=connect[a][0];++i){            t1=(i==0?a:connect[a][i]);            for(int j=0,t2; j<=connect[b][0];++j){                t2=(j==0?b:connect[b][j]);                for(int k=0,t3;k<=connect[c][0];k++){                    t3=(k==0?c:connect[c][k]);                    //判断冲突----                    if((t1&&t2&&t1==t2)||(t1&&t3&&t1==t3)||(t2&&t3&&t2==t3)) continue;///不能对穿                    if((t1&&t2&&t1==b&&t2==a)) continue;///t1,t2不能对穿                    if((t1&&t3&&t1==c&&t3==a)) continue;///t1,t3不能对穿                    if((t2&&t3&&t2==c&&t3==b)) continue;///t2,t3不能对穿                    ///------------                    if(!vis_back[t1][t2][t3]){                        vis_back[t1][t2][t3]=1;                        que_back[rear][0]=step+1,que_back[rear][1]=t1,que_back[rear][2]=t2,que_back[rear][3]=t3;                        ++rear;                    }                }            }        }        ++fro;    }    return false;}int bfs(){    //初始化    memset(vis_fro,0,sizeof(vis_fro)), memset(vis_back,0,sizeof(vis_back));    int fro_fro(0),fro_back(0), rear_fro(1), rear_back(1);    vis_fro[start[1]][start[2]][start[3]]=true, vis_back[goal[1]][goal[2]][goal[3]]=true;    int step_fro = 0, step_back=0;    que_fro[0][0]=start[0],que_fro[0][1]=start[1],que_fro[0][2]=start[2],que_fro[0][3]=start[3];    que_back[0][0]=goal[0],que_back[0][1]=goal[1],que_back[0][2]=goal[2],que_back[0][3]=goal[3];    ///双向BFS搜索    while((fro_fro < rear_fro) || (fro_back < rear_back)){        ///从前向后搜一层        if(bfs_fro(step_fro, fro_fro, rear_fro)) return step_fro+step_back;        else step_fro++;        ///从后向前搜一层        if(bfs_back(step_back,fro_back,rear_back)) return step_fro + step_back;        else step_back++;    }    return -1;}int main(){    while(scanf("%d%d%d",&w,&h,&n)&&w&&h&&n){        ///读取输入        gets(pic[0]);        for(int i=0;i!=h;i++) gets(pic[i]);        ///--------        ///根据输入初始化建立图        memset(connect,0,sizeof(connect));        all=0;        ///获得编号        for(int i=0;i!=h;i++) for(int j=0;j!=w;j++){            if(pic[i][j]!='#') num[i][j] = ++all;            else num[i][j]=0;        }        ///建立图        for(int i=0;i!=h;i++) for(int j=0;j!=w;j++) if(num[i][j]){            int &pos = num[i][j];            if(num[i+1][j]) connect[pos][++connect[pos][0]]=num[i+1][j];            if(num[i-1][j]) connect[pos][++connect[pos][0]]=num[i-1][j];            if(num[i][j+1]) connect[pos][++connect[pos][0]]=num[i][j+1];            if(num[i][j-1]) connect[pos][++connect[pos][0]]=num[i][j-1];        }        ///----------        ///寻找初始状态和目标状态(测了一下字母范围只在abc之间)        ///初始化        start[0]=start[1]=start[2]=start[3]=0;        goal[0]=goal[1]=goal[2]=goal[3]=0;        ///寻找初始状态        for(int i=0;i!=h;i++) for(int j=0;j!=w;j++) if(islower(pic[i][j])){            if(pic[i][j]=='a') start[1]=num[i][j];            if(pic[i][j]=='b') start[2]=num[i][j];            if(pic[i][j]=='c') start[3]=num[i][j];        }        ///寻找目标状态        for(int i=0;i!=h;i++) for(int j=0;j!=w;++j) if(isupper(pic[i][j])){            if(pic[i][j]=='A') goal[1]=num[i][j];            if(pic[i][j]=='B') goal[2]=num[i][j];            if(pic[i][j]=='C') goal[3]=num[i][j];        }        printf("%d\n",bfs());    }}
原文链接:http://blog.csdn.net/crazysillynerd/article/details/42681579


1 0