双向bfs和双向dfs
1. 算法分析
当进行的变换是可逆的时候,且规定步数的上限时,可以使用双向dfs或双向bfs从源点和终点一起搜索。这样可以把时间从O(n)->O(n/2)
如果dfs调栈超过1e5时,那么考虑双向bfs
写法技巧
-
双向dfs
第一个dfs先搜索前一半的空间,打表存储所有可达的值
第二个dfs搜索后一半的空间,然后查询是否在前一半空间中出现过 -
双向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
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
using namespace std;
int const N = 6;
string a[N], b[N];
unordered_map da, db;
string S, E;
int n;
queue qa, qb;
// 进行从a->b扩展
int extend(queue &q, unordered_map &da, unordered_map &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;
}