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、付费专栏及课程。

余额充值