zoukankan      html  css  js  c++  java
  • AC自动机模板

    一.AC自动机的简介:

      与KMP算法类似,AC自动机也是用来处理字符串匹配的问题。AC自动机是建立在KMP算法和Tire树的基础上的。(要有KMP和Tire树的基础,才能熟练掌握AC自动机)。

    二.AC自动机的主要步

      构建一个AC自动机并用于匹配需要三个步骤:

      ①将所有的模式串构建一棵Tire树。

      ②对Tire上的所有节点构造前缀指针(fail数组)。(AC自动机的灵魂关键,我在这里卡了很久)

      ③利用前缀指针对主串进行匹配。

    三.AC自动机的操作实现:

    ①建Tire树(与字典树的建树一模一样):

    void insert()
    {
        int len=strlen(s),u=1;
        for(int i=0;i<len;i++)
        {
            int c=s[i]-'A';
            if(!ch[u][c]) ch[u][c]=++tot;
            u=ch[u][c];
        }
        bo[u]=true;
    }//日常操作...

    ②构建fail数组(关键):

    void bfs()//通过BFS构建fail数组
    {
        for(int i=0;i<=25;++i)
            ch[0][i]=1;//为了方便,将0的所有转移边都设为根节点1 
        que[1]=1;fail[1]=0;//若在根节点失配,则无法匹配字符串
        for(int q1=1,q2=1;q1<=q2;++q1)
        {
            int u=que[q1];
            for(int i=0;i<=25;++i)
            {
                if(!ch[u]) ch[u][i]=ch[fail[u]][i];//此处为优化
                else 
                {
                    que[++q2]=ch[u][i];//若有这个儿子则其加队列中
                    int v=fail[u];
                    fail[ch[u][i]]=ch[v][i];//更新fail数组 
                } 
            }
        } 
    }

    ③简单的字符串匹配:

    int query()
    {
        int u=1;
        for(int i=1;i<=m;++i)
        {
            u=ch[u][a[i]];
            if(bo[u]) return 0;//匹配到返回 0 ,否则返回 1 ; 
            //注:如果是记录次数、数目之类的匹配可以在这里动手脚 
        }
        return 1;
    }

    ——By《一本通》

    不得不说书上的代码真的是漏洞百出。

    学完理论之后,拿一道简单(毒瘤)的模板题练练手——AC自动机模板

    本题看似没有什么问题,但却需要高超的卡常技巧。不然500ms的时限过不了。

    第一遍被卡常的代码(本地AC):

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<stack>
    #include<stack>
    #include<map>
    #include<deque>
    #include<cstdlib>
    using namespace std;
    const int N=1001005;
    int ans=0,cnt=0,nex[N],ch[N][30],bo[N],que[N];
    inline void make(char *s)
    {
        int u=1,len=strlen(s);
        for(int i=0;i<len;++i)
        {
            int c=s[i]-'a';
            if(!ch[u][c])
            {
                ch[u][c]=++cnt;
                memset(ch[cnt],0,sizeof(ch[cnt]));
            }
            u=ch[u][c];
        }
        bo[u]++;
    return ;
    }
    inline void bfs()
    {
        for(int i=0;i<=25;++i)
        {
            ch[0][i]=1;
        }
        que[1]=1;nex[1]=0;
        for(int q1=1,q2=1;q1<=q2;++q1)
        {
            int u=que[q1];
            for(int i=0;i<=25;++i)
            {
                if(!ch[u][i]) ch[u][i]=ch[nex[u]][i];
                else 
                {
                    que[++q2]=ch[u][i];
                    int v=nex[u];
                    nex[ch[u][i]]=ch[v][i];
                }
            }
        }
    return;
    }
    inline void find(char *s)
    {
        int u=1,len=strlen(s),c,k;
        for(int i=0;i<=len;++i)
        {
            c=s[i]-'a';
            k=ch[u][c];
            while(k>1)
            {
                ans+=bo[k];
                bo[k]=0;
                k=nex[k];
            }
            u=ch[u][c];
        }
    return;
    }
    int main()
    {
        int n;
        char s[N<<1];
        ans=0;cnt=1;
        memset(bo,0,sizeof(bo));
        for(int i=1;i<26;++i) ch[0][i]=1,ch[1][i]=0;
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
        {
            scanf("%s",s);
            make(s);
        }
        bfs();
        scanf("%s",s);
        find(s);
        printf("%d
    ",ans);
    return 0;
    }

    看了题解的高超卡常代码(AC):

    #include<bits/stdc++.h>
    #define N 500010
    using namespace std;
    queue<int>q;
    struct Aho_Corasick_Automaton//结构体存函数
    {
        int ch[N][26],bo[N],fail[N],cnt;//......
        void insert(char *s)//与字典树一模一样的建树方式 
        {
            int len=strlen(s);
            int now=0;
            for(int i=0;i<len;i++)
            {
                int v=s[i]-'a';
                if(!ch[now][v]) ch[now][v]=++cnt;
                now=ch[now][v];
            }
            bo[now]++;
        }
        void build()//预处理fail数组 
        {
            for(int i=0;i<26;i++)
            {
                if(ch[0][i])
                {
                    fail[ch[0][i]]=0; 
                    q.push(ch[0][i]);
                }
            }
            while(!q.empty())
            {
                int u=q.front();q.pop();
                for(int i=0;i<26;i++)
                {
                    if(ch[u][i])
                    {
                        fail[ch[u][i]]=ch[fail[u]][i];
                        q.push(ch[u][i]);
                    }
                    else
                        ch[u][i]=ch[fail[u]][i];
                }
            }
        }
        int query(char *s)//查询模式串在文本串中出现的次数 
        {
            int len=strlen(s);
            int now=0,ans=0;
            for(int i=0;i<len;i++)
            {
                now=ch[now][s[i]-'a'];
                for(int t=now;t&&~bo[t];t=fail[t])
                {
                    ans+=bo[t];//有查到这个模式串,就ans总数++ 
                    bo[t]=-1;//标记查到过(避免重复计算) 
                }
            }
            return ans;//返回查找到的模式串的次数 
        }
    }AC;
    int n;//要插入模式串的组数 
    char p[1000005];//定义p字符数组
    int main()
    {
        scanf("%d",&n);//输入n 
        for(int i=1;i<=n;i++)
        {
            scanf("%s",p);
            AC.insert(p);//输入并插入模式串 
        }
        AC.build();//插入模式串完成后,开始预处理fail数组 
        scanf("%s",p);//输入文本串 
        int ans=AC.query(p);//匹配 
        printf("%d
    ",ans);//输出 
    return 0;
    }
  • 相关阅读:
    桶排序
    向控件添加变量之后,类中多了什么?
    atan2&sin
    Bug(1)
    十六进制转化二进制[c]
    接口性能分析与优化
    记一次内存泄漏DUMP分析
    偏移二分查找
    iOS开发——自定义密码输入键盘
    iOS开发——手机号,密码,邮箱,身份证号,中文判断
  • 原文地址:https://www.cnblogs.com/lck-lck/p/9601514.html
Copyright © 2011-2022 走看看