时间限制: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());
}
}