zoukankan      html  css  js  c++  java
  • 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

     

  • 相关阅读:
    iOS开发WKWebView Cookie的读取与写入,与UIWebView的Cookie共享
    【拨号】iPhone拨号功能隐藏代码,值得收藏。
    iOS: 获取不变的UDID
    ios 系统设置对应URL
    iOS开发UI篇—多控制器和导航控制器简单介绍
    iOS开发UI篇—以微博界面为例使用纯代码自定义cell程序编码全过程(二)
    iOS开发UI篇—Date Picker和UITool Bar控件简单介绍
    iOS开发UI篇—程序启动原理和UIApplication
    iOS开发UI篇—常见的项目文件介绍
    iOS开发UI篇—以微博界面为例使用纯代码自定义cell程序编码全过程(一)
  • 原文地址:https://www.cnblogs.com/gosick/p/9822707.html
Copyright © 2011-2022 走看看