NOIP2002-字串变换【双向BFS】
NOIP2002-字串变换
Description
已知有两个字串A,BA,B及一组字串变换的规则(至多66个规则):
A_1A1 ->B_1B1
A_2A2 -> B_2B2
规则的含义为:在 AA中的子串 A_1A1 可以变换为B_1B1,A_2A2 可以变换为 B_2B2 …。
例如:AA='abcdabcd'BB='xyzxyz'
变换规则为:
‘abcabc’->‘xuxu’‘udud’->‘yy’‘yy’->‘yzyz’
则此时,AA可以经过一系列的变换变为BB,其变换的过程为:
‘abcdabcd’->‘xudxud’->‘xyxy’->‘xyzxyz’
共进行了33次变换,使得AA变换为BB。
Input
格式如下:
AA BB
A_1A1 B_1B1
A_2A2 B_2B2 |-> 变换规则
... ... /
所有字符串长度的上限为2020。
Output
输出至屏幕。格式如下:
若在1010步(包含1010步)以内能将AA变换为BB,则输出最少的变换步数;否则输出"NO ANSWER!"
Sample Input
abcd xyz
abc xu
ud y
y yz
Sample Output
3
Solution
题目大意是,给定起始状态A和终止转态B,以及一些变换规则,问多少步可以从A变换到B(或大于10步无解)。
显然可以用BFS搜索解决,但是注意一定要判重。这道题如果熟悉STL的话是很容易写出简洁的代码的,我用的string的replace构造转换后的字串,set判重。
这是我的第一份代码:
#include<cstdio>
#include<queue>
#include<set>
#include<utility>
#include<vector>
#include<cstring>
#include<iostream>
using namespace std;
struct node{
string s;int step;
node(string s,int step):s(s),step(step){}//构造函数
};
string A,B,x,y;
vector<pair<string,string> > rule;
set<string> used;
queue<node> q;
bool bfs(){
q.push(node(A,0));
used.insert(A);
while(!q.empty())
{
node h=q.front();q.pop();
if(h.step>10) return false;
for(int i=0;i<rule.size();++i)
{
int x=h.s.find(rule[i].first);//x是可以转换的起始位置
if(x!=-1)
for(int j=x;j!=-1;j=h.s.find(rule[i].first,j+1))//寻找下一个可以转换的位置
{
string tmp=h.s;
tmp.replace(j,rule[i].first.length(),rule[i].second);//把tmp从j开始的 rule[i].first.length()个字符替换成rule[i].second
if(tmp==B){cout<<h.step+1;return true;}
if(!used.count(tmp)){//一定要判重
q.push(node(tmp,h.step+1));
used.insert(tmp);
}
}
}
}
return false;
}
int main()
{
cin>>A>>B;
while(cin>>x>>y) rule.push_back(make_pair(x,y));
if(!bfs()) cout<<"NO ANSWER!";
return 0;
}
在洛谷上28ms就过了,但交到牛客网上T得飞起,第n次被牛客网的超强数据卡了,真想剁了牛客网。
但是本蒟蒻是不会放弃的,下面重点来了:
Optimization(优化)
注意到起始状态A和终止状态B都是确定的,那么我们可不可以从正反两个方向一起向中间搜索呢?答案是肯定的,这就是双向BFS。
效率
从题目来看,每次扩展有k(k<=66)个分支,最多扩展n(n<=10)层,那么BFS的计算量最坏情况近似地为k^n,这个数字还是相当吓人的(怪不得我会T)
但如果用双向BFS的话,时间效率的优化就不仅仅是一半那么简单,而是2*(k^(n/2)),效率大大提升。
实现
实现很简单,建两个队列,一个存正向的,另一个存反向的,很容易想到正反交替扩展,但这样其实是不科学的,会导致两边决策树发展情况失衡,降低时间效率。因此最好的方式应该是选择结点数较少的一边扩展,这样可以最大限度地维持两边决策树的平衡(这里只简单说一下,具体见网上的证明)。
还有一点要注意,题目中的规则是有单向性的,所以正向扩展时应从A_1->B_1,而反向扩展时应从B_1->A_1,这害我查错查了好久。。。
双向BFS优化代码:
#include<cstdio>
#include<queue>
#include<set>
#include<map>
#include<utility>
#include<vector>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
struct node{
string s;int step;
node(string s,int step):s(s),step(step){}
};
string A,B,x,y;
vector<pair<string,string> > rule;
set<string> used[2];
map<string,int> dis[2];//这里要多建一个dis来存到这个状态花的步数
queue<node> q[2];//每一个都建两个,下标0存正向的,下标1存反向的
void expand(int k){
node h=q[k].front();q[k].pop();
for(int i=0;i<rule.size();++i)
{
int x=(k&1)?h.s.find(rule[i].second):h.s.find(rule[i].first);//正反是不同的
if(x!=-1)
for(int j=x;j!=-1;j=(k&1)?h.s.find(rule[i].second,j+1):h.s.find(rule[i].first,j+1))
{
string tmp=h.s;
if(k&1) tmp.replace(j,rule[i].second.length(),rule[i].first);
else tmp.replace(j,rule[i].first.length(),rule[i].second);
if(used[k^1].count(tmp)){cout<<h.step+1+dis[k^1][tmp];exit(0);}//如果另一个方向已经访问过tmp了,两边到tmp的步数之和即解
if(!used[k].count(tmp)){
q[k].push(node(tmp,h.step+1));
dis[k][tmp]=h.step+1;
used[k].insert(tmp);
}
}
}
return ;
}
bool bfs(){
q[0].push(node(A,0)),q[1].push(node(B,0));
dis[0][A]=0,dis[1][B]=0;
used[0].insert(A),used[1].insert(B);
while(!q[0].empty()&&!q[1].empty())
{
if(q[0].front().step+q[1].front().step>10) return false;
q[0].size()<q[1].size()?expand(0):expand(1);//扩展结点数较少的一边
}
return false;
}
int main()
{
cin>>A>>B;
while(cin>>x>>y) rule.push_back(make_pair(x,y));
if(!bfs()) cout<<"NO ANSWER!";
return 0;
}
尽管我懒到用STL的queue,还是成功AC牛客网(话说我的第一份代码是不是因为这个才挂的。。。)
2018-10-20