双向BFS例题

引入

双向BFS指的是,从起点和终点同时往中间搜;

这种搜法是对普通BFS的一个优化;

下面是单向BFS和双向BFS的直观感受;

在这里插入图片描述
在这里插入图片描述

应用场景

一般应用于需要将整体抽象成一个状态的搜索;

因为这样的搜索状态很多,直接单向搜的话,可能会TLE、MLE

比如魔板字串变换

而像武士风度的牛(求最短路)、城堡问题(求连通块及其大小)的优化效果不明显;

实现方式

方式一、

起点扩展一层、终点扩展一层,以此类推;

方式二、

相对于方式一是一个优化;

从当前状态较少的一边开始搜,避免极端情况(即一边状态很多,一边状态很少);

例题

字串变换

字串变换
在这里插入图片描述
在这里插入图片描述

优化程序的简单计算

如果单向搜索的话,假设有6个规则,变换10次;

那么时间为 6 10 6^{10} 610

如果我们双向搜索,只需要搜 2 ∗ 6 5 2*6^5 265;

可以看出是极大的优化了;


思路

这里采用从状态总数较少的一边进行扩展,具体见代码注释;

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <unordered_map>
#include <queue>
#include <string>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

string start,over;

string ra[10],rb[10];//规则 ra(i) -> rb(i)

int n;

typedef unordered_map<string,int> umsi;

int extend(queue<string> &que,umsi& da,umsi& db,string ra[],string rb[]){
    string u = que.front();
    que.pop();
    //枚举起点
    for(int i=0;i<u.size();++i){
        //枚举规则
        for(int j=0;j<n;++j){
            //如果匹配的上
            if(u.substr(i,ra[j].size()) == ra[j]){
                //按规则变换
                string now = u.substr(0,i) + rb[j] + u.substr(i+ra[j].size());
                //如果另一个队列存在now 直接返回
                if(db.count(now)) return db[now] + da[u] + 1;
                //不重复入队
                if(da.count(now)) continue;
                da[now] = da[u] + 1;
                que.push(now);
            }
        }
    }
    //没有接触到对方
    return -1;
}

int bfs(){
    if(start == over) return 0;
    queue<string> qa,qb;//从起点搜的队列、从终点搜的队列
    umsi disa,disb;//距离
    disa[start] = disb[over] = 0;
    qa.push(start);
    qb.push(over);
    //必须两边队列都有值才能继续扩展
    int res;
    while(!qa.empty() && !qb.empty()){
        //从起点扩展
        if(qa.size() <= qb.size()){
            res = extend(qa,disa,disb,ra,rb);
        }//从终点扩展
        else{
            res = extend(qb,disb,disa,rb,ra);
        }
        if(res!= -1 && res <= 10) return res;
    }
    return -1;
}

void solve(){
    cin >> start >> over;
    //[0,n)
    while(cin >> ra[n] >> rb[n]) ++n;
    int ans = bfs();
    if(ans == -1) cout << "NO ANSWER!\n";
    else cout << ans << '\n';
}

int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    solve();
    return 0;
}

小A与小B

题面

传送门

思路

强制双向BFS属于是;

需要注意的是,小B走两格,不一定要直着走,可以转弯的;

那我们对于小B来说,扩展两次即可;

注意每次扩展要将当前队列中的点全部扩展一遍


还有一点,这题目的小A是很神奇的角色,因为他可以穿模

像上一题中,我们扩展是要求两个队列都有元素的情况;

但是这里扩展只需要一边有元素即可,请看这组数据;

在这里插入图片描述
小A穿模卡过去找小B,就很神奇;

因此这里必须写成这样

while(!queA.empty() || !queB.empty())

不能写成这样

while(!queA.empty() && !queB.empty())

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <utility>

using namespace std;

typedef long long ll;

const int N = 1e3 + 10;

int dx[] = {1,-1,0,0,-1,1,1,-1};
int dy[] = {0,0,1,-1,1,1,-1,-1};

int n,m;

char G[N][N];

typedef pair<int,int> pii;

queue<pii> queA;
queue<pii> queB;

bool visA[N][N];
bool visB[N][N];

int step;

bool extend(queue<pii>& que,bool visA[N][N],bool visB[N][N],int k){
    int sz = que.size();
    while(sz--){ //一次扩展一圈
        auto u = que.front();
        que.pop();
        for(int i=0,xx,yy;i<k;++i){
            xx = u.first + dx[i];
            yy = u.second + dy[i];
            if(xx < 1 || xx > n || yy < 1 || yy > m) continue;
            if(G[xx][yy] == '#' || visA[xx][yy]) continue;
            if(visB[xx][yy]) return 1;
            visA[xx][yy] = 1;
            que.push({xx,yy});
        }
    }
    return 0;
}
int bfs(){
    int ax,ay,bx,by;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j){
            if(G[i][j] == 'C') ax = i,ay = j;
            else if(G[i][j] == 'D') bx = i,by = j;
        }
    if(ax == bx && ay == by) return 0;
    queA.push({ax,ay});
    queB.push({bx,by});
    visA[ax][ay] = 1;
    visB[bx][by] = 1;
    step = 0;
    while(!queA.empty() || !queB.empty()){
        ++step;
        if(extend(queA,visA,visB,8)) return step;
        if(extend(queB,visB,visA,4)) return step;
        if(extend(queB,visB,visA,4)) return step;
    }
    return -1;
}

void solve(){
    cin >> n >> m;
    char ch;
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j){
            while(cin >> ch,ch == ' ');
            G[i][j] = ch;
        }
    }
    int res = bfs();
    if(res != -1){
        cout << "YES\n";
        cout << res << '\n';
    }
    else cout << "NO\n";
}

int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    solve();
    return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值