190. 字串变换
已知有两个字串 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=aa, B=bb
变换规则为:
a → b
此时,不能将两个 a 在一步中全部转换为 b,而应当分两步完成。
输入格式
输入格式如下:
A B
A1 B1
A2 B2
… …
第一行是两个给定的字符串 A和 B。
接下来若干行,每行描述一组字串变换的规则。
所有字符串长度的上限为 20。
输出格式
若在 10 步(包含 10 步)以内能将 A 变换为 B ,则输出最少的变换步数;否则输出 NO ANSWER!。
输入样例:
abcd xyz
abc xu
ud y
y yz
输出样例:
3
难度:中等
按照题目条件,本题最多需要搜索十层;设每层根据规则展开
K
K
K个字符串,则最坏搜索情况下时间复杂度达到
K
10
K^{10}
K10,很容易超时;
此时结合题设条件可以采用双向广搜,A向B搜索的同时,B也向A一层一层的展开搜索(本题中A可以根据变化到B其实B也可以按照反方向向A变化);当双向搜索的时候,搜索宽度会变窄,对时间的优化度明显有提升
注意每次搜索都必须将一层搜索完,此时统计的步数才是正确的
#include<bits/stdc++.h>
using namespace std;
const int N = 10;
string A, B;
string a[N], b[N];
int n = 0;
//A->B 或者 B->A的拓展规则相同,这里抽象出这个拓展函数
//参数分别是拓展字符队列,da是到队首的距离,db是到队尾的距离,a->b是一个匹配规则
int extend(queue<string>& q, unordered_map<string,int>& da, unordered_map<string,int>& db, string a[N], string b[N])
{
//将在同一层的点全部搜索完,注意一定要保证同层搜索完毕,这样才能保证步数是准确的
int dis = da[q.front()]; //记录当前的层数
while(q.size() && da[q.front()] == dis){
string now = q.front(); q.pop();
for(int i = 0;i < n;i ++){
for(int j = 0;j < now.size();j ++){
//对于每一个规则,看当前字符串中有没有符合转化规则的字符字串
if(now.substr(j, a[i].size()) == a[i]){
//对字串进行替换:目标字符之前的+替换字符+替换子串之后的
string state = now.substr(0, j) + b[i] + now.substr(j + a[i].size());
//如果从相反方向已经遍历到这个字符,注意因为是广搜,某个字符第一次被找到就是最短路径
if(db.count(state)) return da[now] + db[state] + 1;
if(da.count(state)) continue; //如果已经被从起点已经被搜索过的话,直接跳过(当前不是距离起点的最短距离)
da[state] = da[now] + 1;
q.push(state);
}
}
}
}
return 11; //返回一个比10大的数字,表示此轮搜索没有找到交叉字符
}
int bfs()
{
if(A == B) return 0; //将情况考虑全面,首先考虑二者相等
queue<string> qa, qb;
unordered_map<string,int> da, db; //用于存储某个字符串到起点的距离及到终点的距离
//初始化
qa.push(A); qb.push(B);
da[A] = 0; db[B] = 0;
int step = 0;
while(qa.size() && qb.size()){
int t;
//从拓展层数较少的一段开始
if(qa.size() < qb.size()) t = extend(qa, da, db, a, b);
else t = extend(qb, db, da, b, a);
if(t <= 10) return t; //搜索到了交叉字符
step ++;
if(step == 10) //搜索了十层都没有找到交叉字符,不符合题意,直接返回
return -1;
}
return -1;
}
int main()
{
cin >> A >> B;
while(cin>>a[n]>>b[n]) n ++;
int cnt = bfs();
if(cnt == -1) puts("NO ANSWER!");
else cout<<cnt<<endl;
return 0;
}