题解 P1032 [NOIP2002 提高组] 字串变换
题目描述
给定一些规则(不多于 \(6\) 个),试求把字符串 \(A\) 变为字符串 \(B\) 最少需要变换几次?
规则:对于第 \(i\) 个规则 \((a_i,b_i)\) ,若 \(a_i\) 是一个字符串的子串,则可以把这个字符串中的一个 \(a_i\) 变成 \(b_i\) 。
字符串长度不多于 \(20\) 。
Solution
一种比较直接的想法是从 \(A\) 开始 BFS ,然后枚举所有能变换成的字符串,更新变成的这个字符串的最少操作步数。
这类似于边权全为 \(1\) 的有向图求最短路的 BFS ,但因为操作的次数很多,复杂度会很高。
考虑双向搜索:从 \(A\) 开始正向搜索,从 \(B\) 开始反向搜索,每次搜索拓展一层,最终如果两边都搜到了同一个状态,那么第一个搜到的状态的两边的步数之和就是最小操作次数。
双向搜索每次拓展的时候可以选一个当前待拓展状态数量少的方向进行搜索。每次拓展一整层。
代码如下:
#include <iostream>
#include <algorithm>
#include <iostream>
#include <queue>
#include <string>
#include <unordered_map>
using namespace std;
unordered_map <string ,int> da ,db;
queue <string> qa ,qb;
string a[10] ,b[10]; int n;
inline int bfs(string st ,string ed) {
if (st == ed) return 0;
qa.push(st); qb.push(ed);
da[st] = db[ed] = 0;
while (!qa.empty() && !qb.empty()) {
if (qa.size() <= qb.size()) {
int t = qa.size();
while (t--) {
string now = qa.front(); qa.pop();
if (da[now] >= 10) return -1;
for (int i = 0; i < (int)now.size(); i++)
for (int j = 1; j <= n; j++)
if (now.substr(i ,a[j].size()) == a[j]) {
string s = now.substr(0 ,i) + b[j] + now.substr(i + a[j].size());
if (da.count(s)) continue;
da[s] = da[now] + 1;
if (db.count(s)) return db[s] + da[s];
qa.push(s);
}
}
}
else {
int t = qb.size();
while (t--) {
string now = qb.front(); qb.pop();
if (da[now] >= 10) return -1;
for (int i = 0; i < (int)now.size(); i++)
for (int j = 1; j <= n; j++)
if (now.substr(i ,b[j].size()) == b[j]) {
string s = now.substr(0 ,i) + a[j] + now.substr(i + b[j].size());
if (db.count(s)) continue;
db[s] = db[now] + 1;
if (da.count(s)) return da[s] + db[s];
qb.push(s);
}
}
}
}
return -1;
}
signed main() {
ios :: sync_with_stdio(false);
string st ,ed;
cin >> st >> ed;
n = 1;
while (cin >> a[n] >> b[n]) n++;
n--;
int ans = bfs(st ,ed);
if (ans == -1) cout << "NO ANSWER!" << endl;
else cout << ans << endl;
return 0;
}