双向bfs和双向dfs

1. 算法分析

当进行的变换是可逆的时候,且规定步数的上限时,可以使用双向dfs或双向bfs从源点和终点一起搜索。这样可以把时间从O(n)->O(n/2)
如果dfs调栈超过1e5时,那么考虑双向bfs

写法技巧

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。
  1. 双向dfs
    第一个dfs先搜索前一半的空间,打表存储所有可达的值
    第二个dfs搜索后一半的空间,然后查询是否在前一半空间中出现过

  2. 双向bfs
    维护两个队列,当两个队列均非空时才能继续进行循环。循环内不断对元素较少的那个队列进行bfs操作

2. 例题

2.1 双向dfs

acwing171 送礼物
达达帮翰翰给女生送礼物,翰翰一共准备了N个礼物,其中第i个礼物的重量是G[i]。
达达的力气很大,他一次可以搬动重量之和不超过W的任意多个物品。
达达希望一次搬掉尽量重的一些物品,请你告诉达达在他的力气范围内一次性能搬动的最大重量是多少。
N~46, W,G[i]~int

/*
第一个dfs打表记录前n/2的所有可能和
第二个dfs找到小于等于w-sum的最大的那个
*/
#include <bits/stdc++.h>

using namespace std;

int const N = 47;
int a[N], cnt = 1, weight[1 << 25], w, n, k, ans;

void dfs1(int u, int sum) {
    if (u == k) {
        weight[cnt++] = sum;
        return;
    }
    
    dfs1(u + 1, sum);  // 不选第u个
    if (sum + 0ll + a[u] <= w) dfs1(u + 1, sum + a[u]);  // 选第u个
}

void dfs2(int u, int sum) {
    
    // 当找完后n/2个后,二分查找小于等于w-s的最大值
    if (u >= n) {
        int l = 0, r = cnt - 1;
        while (l < r) {
            int mid = (l + r + 1) / 2;
            if (sum + 0ll + weight[mid] <= w) l = mid;
            else r = mid - 1;
        }
        ans = max(ans, sum + weight[l]);
        return;
    }
    
    dfs2(u + 1, sum);  // 不选第u个
    if (sum + 0ll + a[u] <= w) dfs2(u + 1, sum + a[u]);  // 选第u个
}

int main() {
    cin >> w >> n;
    int num = 0;
    for (int i = 0, t; i < n; ++i) {
        cin >> t;
        if (t <= w) a[num++] = t;
    }
    n = num;
    k = n / 2;
    
    // 优先大的先搜索
    sort(a, a + n);
    reverse(a, a + n);
    dfs1(0, 0);
    
    // 去重
    sort(weight, weight + cnt);
    cnt = unique(weight, weight + cnt) - weight;
    
    // 第二个dfs
    dfs2(k, 0);
    cout << ans;
    return 0;
}

2.2 双向bfs

acwing190字串变换
已知有两个字串 A, B 及一组字串变换的规则(至多6个规则):
A1 -> B1
A2 -> B2

规则的含义为:在 A 中的子串 A1 可以变换为 B1、A2 可以变换为 B2 …。
例如:A=’abcd’ B=’xyz’
变换规则为:
‘abc’->‘xu’ ‘ud’->‘y’ ‘y’->‘yz’
则此时,A 可以经过一系列的变换变为 B,其变换的过程为:
‘abcd’->‘xud’->‘xy’->‘xyz’
共进行了三次变换,使得 A 变换为B。
若在 10 步(包含 10步)以内能将 A 变换为 B ,则输出最少的变换步数;否则输出”NO ANSWER!”

// 设置两个队列,两个队列都非空的时候才能进行搜索
// 如果a队列元素比b队列少,那么处理a队列;否则处理b队列
#include <bits/stdc++.h>

using namespace std;

int const N = 6;
string a[N], b[N];
unordered_map<string, int> da, db;
string S, E;
int n;
queue<string> qa, qb;

// 进行从a->b扩展
int extend(queue<string> &q, unordered_map<string, int> &da, unordered_map<string, int> &db, string a[], string b[]) {
    auto t = q.front();
    q.pop();
    
    for (int i = 0; i < t.size(); ++i) {  // 遍历字符串每一位
        for (int j = 0; j < n; ++j) {  // 枚举每一种变换
            if (t.substr(i, a[j].size()) != a[j]) continue;  // 如果不能替换,continue
            string new_str = t.substr(0, i) + b[j] + t.substr(i + a[j].size());  // 生成新的字符串
            if (da.count(new_str)) continue;  // 如果先前走过
            if (db.count(new_str)) return db[new_str] + da[t] + 1;  // 如果找到
            da[new_str] = da[t] + 1;  // 记录
            q.push(new_str);
        }
    }
    
    return 11;
}

int bfs() {  // bfs判断能否到达
    qa.push(S), qb.push(E);
    da[S] = 0, db[E] = 0;
    
    while (qa.size() && qb.size()) {  // 当两个队列都不为空才有可能可达
        int t;  
        if (qa.size() <= qb.size()) t = extend(qa, da, db, a, b);  // a队列数目少,那么扩展a队列使之变多
        else t = extend(qb, db, da, b, a);  // 否则扩展b队列
        
        if (t <= 10) return t;  // 如果步数小于等于10,那么找到了
    }
    return 11;
}

int main() {
    cin >> S >> E;  // 起始状态和终止状态
    while (cin >> a[n] >> b[n]) n++;  // 读入变换
    
    int ans = bfs();  // 如果步数大于10,就是无法到达
    if (ans > 10) cout << "NO ANSWER!";
    else cout << ans;
    return 0;
}
扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄