题目描述:
已知有两个字串 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。
输入格式
输入格式如下:
A B
A1 B1 \
A2 B2 |-> 变换规则
… … /
所有字符串长度的上限为 20。
输出格式
若在 10 步(包含 10步)以内能将 A 变换为 B ,则输出最少的变换步数;否则输出”NO ANSWER!”
输入样例:
abcd xyz
abc xu
ud y
y yz
输出样例:
3
分析:
本题如果采用普通的BFS求状态转换的最小步数,字符串最长长度是20,假设每一个字符上最多只能进行一种变换,十步以内最多可以进行20^10也就是1024*10^10种状态的搜索,显然这么大的数字会超时或者超空间。即使每个字符串只会做一次变换,也一共要做6^10也就是六千多万种状态的搜索。因为BFS搜索过程中状态的数量是指数级递增的,所以时间增长的特别快。假设从A到B状态一共要做十次转化,C是A到B的一个中间状态,如果A到C要做5次转化,则C到B还要做5次转化。我们同时从A和B进行BFS,各进行五次搜索都搜到C的时候停止,则在两种情况下涉及的状态分别是2 * 20^5=640w以及2 * 6^5 约等于1.54w个状态,显然,这两个状态数都要远小于之前的状态数,或者说只有之前状态数开根号后这么多状态。因此,本题分别从起点和终点同时进行BFS的这种双向BFS的方法能够很好的解决问题。
需要解决的就是如何同时进行BFS搜索,可以先将开始状态和终点状态分别加入两个不同的队列,如何当两个队列都非空的时候,先对较小的那个队列出队做BFS,以减小状态的增加数量,并且如果两个队头元素的步数之和超过了10,就可以剪枝直接返回了。状态的转换需要考虑从状态x能够转换到哪些状态,因为字符串较短这里直接暴力搜索就可以解决了,具体实现见代码:
#include <iostream>
#include <queue>
#include <algorithm>
#include <string>
#include <unordered_map>
using namespace std;
const int N = 6;
string a[N],b[N];
int n;
unordered_map<string,int> d1,d2;
queue<string> q1,q2;
int extend(queue<string> &q,unordered_map<string,int> &d,string *a,string * b){
string 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;
string u = t.substr(0,i) + b[j] + t.substr(i + a[j].size());
if(d.count(u)) continue;
d[u] = d[t] + 1;
if(d1.count(u) && d2.count(u)) return d1[u] + d2[u];
q.push(u);
}
}
return -1;
}
int bfs(string x,string y){
q1.push(x),q2.push(y);
d1[x] = 0,d2[y] = 0;
while(q1.size() && q2.size()){
if(d1[q1.front()] + d2[q2.front()] > 11) return 11;
int t;
if(q1.size() <= q2.size()) t = extend(q1,d1,a,b);
else t = extend(q2,d2,b,a);
if(t != -1) return t;
}
return 11;
}
int main(){
string A,B;
cin>>A>>B;
while(cin>>a[n]>>b[n]) n++;
int res = bfs(A,B);
if(res > 10) puts("NO ANSWER!");
else cout<<res<<endl;
return 0;
}