2018牛客网暑期ACM多校训练营(第九场)F(AC自动机)

题目描述:

    有n(n<=4)个长度为len的字符串,以及一个长度为len2的操作串。每一次你将选取操作串中长度为i(0<=i<=len2)的前缀,问你最少在这个前缀后加多少个字符,使得新字符串的后缀中能够至少出现这n个字符串中的一个。

题目分析:

    因为题目中设计多个串的匹配一个长串的问题,我们可以考虑使用AC自动机进行处理。

    再考虑题目中要求我们求出匹配串的后缀凑出模式串的最小长度,因此,我们可以将这样的一个问题转化成:在一个Trie图中,处于第i个结点的字符到达任意一个模式串终点j的最短路。

    因此,我们只需要利用AC自动机,将利用失配指针的性质,先将整张Trie图建立出来,并将每一个结点到达任意终点的最短路用bfs求出即可。

    具体细节请看代码。

代码:

#include <bits/stdc++.h>
#define maxn 400005
using namespace std;
const int INF=0x3f3f3f3f;
char tmp[maxn];
vector<int>vec[maxn];
struct Trie{//AC自动机
    int next[maxn][26],fail[maxn],root,id,ans[maxn];//ans[i]记录了某个结点是否处于字符串的末尾
    set<int>st;//set用来统计每个单词的结尾的坐标
    int newnode(){//新建立结点
        for(int i=0;i<26;i++){
            next[id][i]=-1;
        }
        ans[id]=INF;
        return id++;
    }
    void init(){
        st.clear();
        id=0;
        root=newnode();
    }
    void Insert(char *str){//将模式串插入Trie树中
        int len=strlen(str);
        int now=root;
        for(int i=0;i<len;i++){
            if(next[now][str[i]-'a']==-1){
                next[now][str[i]-'a']=newnode();
            }
            now=next[now][str[i]-'a'];
        }
        st.insert(now);//将模式串结尾插入set中
        ans[now]=0;//ans[i]=0 记录了某个结点曾今
    }
    void build(){//建立AC自动机失配指针
        queue<int>que;
        fail[root]=root;
        for(int i=0;i<26;i++){
            if(next[root][i]==-1){
                next[root][i]=root;
            }
            else{
                fail[next[root][i]]=root;
                que.push(next[root][i]);
            }
        }
        while(!que.empty()){
            int now=que.front();
            que.pop();
            //如果某个结点的失配指针正好等于某个模式串的终点,则证明该结点也必定跟失配指针所指的结点相同,也可以作为终点处理
            if(ans[fail[now]]==0){
                st.insert(now);
                ans[now]=0;
            }
            for(int i=0;i<26;i++){
                if(next[now][i]==-1){
                    next[now][i]=next[fail[now]][i];
                }
                else{
                    fail[next[now][i]]=next[fail[now]][i];
                    que.push(next[now][i]);
                }
            }
        }
    }

    void bfs(){//bfs处理最短路
        for(int i=0;i<id;i++){
            vec[i].clear();
        }
        for(int i=0;i<id;i++){
            for(int j=0;j<26;j++){
                vec[next[i][j]].push_back(i);//建立Trie图的过程
            }
        }
        queue<int>que;
        for(auto it:st){//首先将可以作为终点的结点压入队列
            que.push(it);
        }
        while(!que.empty()){//扩展距离
            int now=que.front();
            que.pop();
            for(auto to:vec[now]){
                if(ans[to]!=INF) continue;
                ans[to]=ans[now]+1;
                que.push(to);
            }
        }
    }
    void query(char *str){//查询答案
        int len=strlen(str);
        stack<int>sta;//用栈去维护模板串
        int now=root;
        cout<<ans[now]<<endl;
        for(int i=0;i<len;i++){
            if(str[i]=='-'&&sta.empty()){//如果当前的栈顶为空,且当前字符还是'-',则直接将now变为根节点
                now=root;
            }
            else if(str[i]=='-'){
                now=sta.top();
                sta.pop();
            }
            else{
                sta.push(now);
                now=next[now][str[i]-'a'] ;
            }
            cout<<ans[now]<<endl;
        }
    }
}ac;
int main()
{
    int n;
    scanf("%d",&n);
    ac.init();
    while(n--){
        scanf("%s",tmp);
        ac.Insert(tmp);
    }
    ac.build();
    ac.bfs();
    scanf("%s",tmp);
    ac.query(tmp);
    return 0;
}

 

posted @ 2018-08-16 22:49  ChenJr  阅读(201)  评论(0编辑  收藏  举报