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

给出一个最大为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

时间: 2024-10-09 22:08:58

UVa1601 - The Morning after Halloween(单向+双向BFS)的相关文章

UVa 1601 || POJ 3523 The Morning after Halloween (BFS || 双向BFS &amp;&amp; 降维 &amp;&amp; 状压)

题意 :w*h(w,h≤16)网格上有n(n≤3)个小写字母(代表鬼).要求把它们分别移动到对应的大写字母里.每步可以有多个鬼同时移动(均为往上下左右4个方向之一移动),但每步结束之后任何两个鬼不能占用同一个位置,也不能在一步之内交换位置.输入保证所有空格连通,所有障碍格也连通,且任何一个2*2子网格中至少有一个障碍格.输出最少的步数.输入保证有解. 分析 :可以将当前所有小鬼的位置作为一个状态,然后模拟小鬼移动BFS拓展出其他可行状态并且顺带记录步数,直到所有的小鬼都到达终点.首先状态如何表示

POJ1915Knight Moves(单向BFS + 双向BFS)

题目链接 单向bfs就是水题 1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <algorithm> 5 #include <queue> 6 using namespace std; 7 const int INF = 0x3f3f3f3f; 8 const int Max = 300 + 5; 9 struct Node 10 { 11 int

POJ 1915-Knight Moves (单向BFS &amp;&amp; 双向BFS 比较)

题目链接:Knight Moves 研究了一下双向BFS,不是很难,和普通的BFS一样,双向BFS不过是从 起点和终点同时开始搜索,可减少搜索时间 当两条搜索路线相遇时,结束. 貌似有一年百度的招聘 笔试,就是双向BFS.... 下面,比较一下BFS 和 双向BFS的用时: BFS STL的queue可能会浪费一点时间 #include <iostream> #include <cstdio> #include <cstdlib> #include <cstrin

HDU1195 双向BFS(或BFS)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1195 , 双向BFS或者直接BFS也可以过. 其实这道题只是单向BFS就可以过的,但是为了练算法,所以还是用了双向BFS来写. 算法: 先预处理一下,从1111到9999的所有点进行构图(由于是1~9的,所以除去含有0元素的数字),能进行一次变换变成的数字则表示两点之间连通.然后从初态与目态两个点进行BFS,如果有轨迹重合的就返回路程和. 这里注意双向BFS要一层一层的进行搜索,不然的话会产生错误,

HDU 1043 Eight(双向BFS+康托展开)

http://acm.hdu.edu.cn/showproblem.php?pid=1043 题意:给出一个八数码,求出到达指定状态的路径. 思路:路径寻找问题.在这道题里用到的知识点挺多的.第一次用双向BFS来做. ①双向BFS 在单向BFS的基础上,多建一个从终止状态开始搜索的队列,当然这个时候需要两个vis[]辅助数组,分别记录两个队列的访问情况,当两个队列相遇时即可终止循环. ②康托展开 X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[

双向BFS(转)

(转) 双向BFS(http://www.cppblog.com/Yuan/archive/2011/02/23/140553.aspx) 如果目标也已知的话,用双向BFS能很大提高速度 单向时,是 b^len的扩展. 双向的话,2*b^(len/2)  快了很多,特别是分支因子b较大时 至于实现上,网上有些做法是用两个队列,交替节点搜索 ×,如下面的伪代码:    while(!empty()){ 扩展正向一个节点 遇到反向已经扩展的return 扩展反向一个节点 遇到正向已经扩展的retur

【HDU3085】nightmare2 双向BFS

对于搜索树分支很多且有明确起点和终点的情况时,可以采用双向搜索来减小搜索树的大小. 对于双向BFS来说,与单向最大的不同是双向BFS需要按层扩展,表示可能到达的区域.而单向BFS则是按照单个节点进行扩展,因为只有当前状态. 代码如下: #include <bits/stdc++.h> using namespace std; const int maxn=810; char mp[maxn][maxn]; int n,m,tot,step,f; struct node{ int x,y; }b

https 单向双向认证说明_数字证书, 数字签名, SSL(TLS) , SASL

转自:https 单向双向认证说明_数字证书, 数字签名, SSL(TLS) , SASL 因为项目中要用到TLS + SASL 来做安全认证层. 所以看了一些网上的资料, 这里做一个总结. 1. 首先推荐几个文章: 数字证书: http://www.cnblogs.com/hyddd/archive/2009/01/07/1371292.html 数字证书和SSL: http://www.2cto.com/Article/201203/121534.html 数字签名: http://www.

Hdu1401-Solitaire(双向bfs)

Solitaire is a game played on a chessboard 8x8. The rows and columns of the chessboard are numbered from 1 to 8, from the top to the bottom and from left to right respectively.There are four identical pieces on the board. In one move it is allowed to