.字符串变换
题目链接
题目描述:
已知有两个字串 A, B 及一组字串变换的规则(至多 66 个规则):
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。
输出格式
若在 1010 步(包含 1010 步)以内能将 A变换为 B ,则输出最少的变换步数;否则输出 NO ANSWER!。
输入样例:
abcd xyz
abc xu
ud y
y yz
输出样例:
3
分析
如果直接使用bfs搜索,每个点是一个长为20的字符串,字符串每个字母都有可能可以扩展,并且最多可以有6种扩展方式,所以每个点最多可以扩展出120个点,扩展10层,搜索次数为12010,时间复杂度O((LN)10)。使用双向广搜,每个方向只需要扩展10层,时间复杂度可以降低为O((LN)5) 。
解法一:
简单粗暴的正向搜索五层,再反向搜索五层,用两个map存储搜索得到的元素和距离。如果搜索过程中直接找到终点,说明答案小于5步,直接返回答案。如果还没搜索到五层队列已经为空,说明无法到达终点。如果搜索五层后队列不为空,则答案可能大于10步,此时正向搜索和反向搜索队列中都只剩下第五层的元素,任选其中一个,将元素全都取出,与反向搜索得到的元素比对。若反向搜索过此元素,说明能够在10步内找到终点,如果所有元素都没有在反向搜索过程中搜索过,说明无法在10步内找到终点。
实现代码:
#include<bits/stdc++.h>
using namespace std;
string A,B;
string a[6],b[6];
unordered_map<string,int>dista,distb;
queue<string>qa,qb;
int idx;//规则数量
int bfs(string A,string B,string a[],string b[],unordered_map<string,int>&dist,queue<string>&q)
{
if(A==B) return 0;
dist[A]=0;
q.push(A);
while(q.size())
{
auto t=q.front();//取出t开始扩展
if(dist[t]==5) return -1;//当取出的t是第五层的点,说明前五步能扩展得到的点已经存入dist,五步之内没有找到
q.pop();
//cout<<t<<" "<<dist[t]<<endl;
for(int i=0;i<t.size();i++)//遍历t的每一个字符
{
for(int j=0;j<idx;j++)//遍历规则
{
if(t.substr(i,a[j].size())==a[j])//可以变换
{
string temp=t.substr(0,i)+b[j]+t.substr(i+a[j].size());//变换后
if(dist.count(temp)==0)//第一次遍历
{
dist[temp]=dist[t]+1;
q.push(temp);
}
if(temp==B) return dist[temp];//已经找到目标
}
}
}
}
return -2;//所有可能变换得到的点已经遍历完毕,无法达到终点。
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>A>>B;
while(cin>>a[idx]>>b[idx])
{
idx++;
}
int dist1=bfs(A,B,a,b,dista,qa);
int dist2=bfs(B,A,b,a,distb,qb);
//cout<<"dist1="<<dist1<<" "<<"dist2="<<dist2<<endl;
if(dist1==-2) cout<<"NO ANSWER!"<<endl;
else if(dist1==-1)//大于五步的情况
{
while(qa.size())//取出qa第五层的点与qb所有的点比对看是否相遇
{
auto t=qa.front();
qa.pop();
if(distb.count(t))//qb中有这个点
{
cout<<dista[t]+distb[t]<<endl;
return 0;
}
}
cout<<"NO ANSWER!"<<endl;//十步之内没有答案
}
else cout<<dist1<<endl;//少于五步直接相遇
return 0;
}
解法二:
双向同时进行搜索,每次扩展一层,每次扩展过程中都判断该点在另一个搜索方向过程中有没有被搜索过。
实现代码:
#include<bits/stdc++.h>
using namespace std;
string A,B;
string a[6],b[6];
unordered_map<string,int>da,db;
queue<string>qa,qb;
int idx;//规则数量
//扩展函数,每次将某个搜索方向扩展一层,搜索成功则返回小于实际步数,否则返回大于10的数
int extand(queue<string>&q,unordered_map<string,int>&da,unordered_map<string,int>&db,string a[],string b[])
{
int d=da[q.front()];//记录当前所在层数
while(q.size()&&da[q.front()]==d)//扩展一层
{
auto t=q.front();//取出t开始扩展
q.pop();
//cout<<t<<" "<<dist[t]<<endl;
for(int i=0;i<t.size();i++)//遍历t的每一个字符
{
for(int j=0;j<idx;j++)//遍历规则
{
if(t.substr(i,a[j].size())==a[j])//可以变换
{
string temp=t.substr(0,i)+b[j]+t.substr(i+a[j].size());//变换后
if(da.count(temp)==0)//第一次遍历
{
da[temp]=da[t]+1;
q.push(temp);
}
if(db.count(temp)) return da[temp]+db[temp];//在另一个方向也搜索到该点
}
}
}
}
return 11;//扩展完一层还是没有找到终点
}
//搜索成功返回步数,否则返回-1
int bfs()
{
if(A==B) return 0;
da[A]=db[B]=0;
qa.push(A);
qb.push(B);
int step=0;//记录步数
while(qa.size()&&qb.size())//如果某个方向搜索完毕还没找到终点说明无解
{
int t;//记录答案
if(qa.size()<qb.size()) t=extand(qa,da,db,a,b);//正向元素少,进行正向搜索
else t=extand(qb,db,da,b,a);//反向搜索
if(t<=10) return t;//搜索成功的标准
if(++step==10) return -1;//超过10步
}
return -1;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>A>>B;
while(cin>>a[idx]>>b[idx])
{
idx++;
}
int dist=bfs();
if(dist==-1) cout<<"NO ANSWER!"<<endl;
else cout<<dist<<endl;
return 0;
}