NOIP2002-字串变换【双向BFS】

NOIP2002-字串变换

Description

已知有两个字串A,BA,B及一组字串变换的规则(至多66个规则):

A_1A1 ->B_1B1

A_2A2 -> B_2B2

规则的含义为:在 AA中的子串 A_1A1 可以变换为B_1B1A_2A2 可以变换为 B_2B2 …。

例如:AA='abcdabcd'BB='xyzxyz'

变换规则为:

abcabc’->‘xuxu’‘udud’->‘yy’‘yy’->‘yzyz’

则此时,AA可以经过一系列的变换变为BB,其变换的过程为:

abcdabcd’->‘xudxud’->‘xyxy’->‘xyzxyz’

共进行了33次变换,使得AA变换为BB。

Input

格式如下:

ABB
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;
} 
View Code

在洛谷上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;
} 
View Code

 

尽管我懒到用STL的queue,还是成功AC牛客网(话说我的第一份代码是不是因为这个才挂的。。。)

2018-10-20

 

posted @ 2018-10-20 19:43  青君  阅读(379)  评论(1编辑  收藏  举报