UVa 1601 - The Morning after Halloween(双向BFS版)

本文介绍了如何使用双向BFS优化算法解决UVA 1601问题,避免单向BFS可能导致的组合性爆炸。双向BFS通过从起点和终点同时搜索并在中间相遇来提高效率。文章讨论了错误的交替节点搜索方法,并解释了正确做法是一层层地搜索,确保同一层的节点被完整处理。将原来的单向BFS改为双向BFS后,运行时间从3.6秒降低到2.4秒。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

时间限制:12.000

题目链接:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4476

  接下来继续上次单向BFS的版本,接下来向双向BFS的方向优化。

  那什么是双向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 
  
   
#include 
   
    
#include 
    
     
#include 
     
      
#include 
      
       

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());
    }
}

      
     
    
   
  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值