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;
}
原文地址:https://www.cnblogs.com/lck-lck/p/9601514.html