学习笔记:AC自动机

五迷三道 提交于 2020-03-10 18:35:31

这里的\(AC\)不要理解错啊......
让一个文本串跑好多模式串的\(KMP\),全称\(Aho-Corasick\;automaton\)
很神奇,就像面条机一样
将线形的字符串改成树形的\(Trie\)了,将令人懵逼的\(next[]\)数组改为难以理解的失配指针\(fail\)
引用大佬\(yyb\)的话:
\[Trie\text{树的失配指针是指向:沿着其父节点的失配指针,一直向上,直到找到拥有当前这个字母的子节点的节点的那个子节点}\]

再盗张图吧:

于是我们可以先建一颗平常的\(Trie\):
这里是结构体:

struct node
{
    int kid[28];//对应的儿子节点(a-0,z-25)
    int end,fail;//分别是有几个子串在此终结,fail指针
}ac[MAXN];

插入操作(大家都会的)

void add(char *s)
{
    int len=strlen(s),u=0;
    for(int i=0;i<len;i++)
    {
        int j=s[i]-'a';
        if(!ac[u].kid[j]) ac[u].kid[j]=++cnt;
        u=ac[u].kid[j];
    }
    ac[u].end++;
}

接下来就是寻找每个节点的失配指针了:
思路是这样的:
对于每个节点,枚举所有可能的儿子节点(a--z),如果存在这个节点,就把这个儿子节点的失配指针指向他父亲失配指针对应的儿子节点。
那你会问了:如果他父亲失配指针对应的节点没有相应的子节点呢?
\(qwq\),那是直接自然给成\(0\)了。
然后忽然想起(勿喷):


嗯,如果没有这个子节点,就建一个虚的子节点指向他父亲失配指针对应的儿子节点。
可以借助队列实现:
void build() 
{
    queue<int> q;
    int u=0;
    for(int i=0;i<26;i++)
    {
        if(ac[u].kid[i]) ac[ac[u].kid[i]].fail=0,q.push(ac[u].kid[i]);;
        
    }
    while(!q.empty())
    {
        u=q.front();
        q.pop();
        for(int i=0;i<26;i++)
        {
            if(ac[u].kid[i])
            {
                ac[ac[u].kid[i]].fail=ac[ac[u].fail].kid[i];
                q.push(ac[u].kid[i]);
            }
            else ac[u].kid[i]=ac[ac[u].fail].kid[i];
        }
    }
    return;
}

后面,就可以匹配了,实现了真·自动!
大体就是对于每个节点儿子节点的失配指针跳来跳去就好了。
注意要将统计了的节点进行标记(习惯将统计结尾数标为-1),以避免重复运算。
就是这样的:

int countt(char *s)
{
    int len=strlen(s),u=0;
    for(int i=0;i<len;i++)
    {
        u=ac[u].kid[s[i]-'a'];
        for(int k=u;k&&ac[k].end!=-1;k=ac[k].fail)
        {
            ans+=ac[k].end;
            ac[k].end=-1;
        }
    }   
    return ans;
}

据我看来,时间复杂度是\(O(type(\sum len_{\text{模式串}}+len_{\text{文本串}}))\)(这里的\(type\)代表字符种类数),可以通过本题。
好了,\(AC\)自动机的简单教程已经完成了,那就去做板子题吧:
题目链接:P3808 【模板】AC自动机(简单版)
对了,这题作为我\(AC\)的第\(300\)道题,有一些深层意义呢(你知道的)。
下面粘上代码:

\(Code\):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int MAXN=1000005;
struct node
{
    int kid[28];
    int end,fail;
}ac[MAXN];
int cnt=0,ans=0;
int n;
char ch[MAXN];
void add(char *s)
{
    int len=strlen(s),u=0;
    for(int i=0;i<len;i++)
    {
        int j=s[i]-'a';
        if(!ac[u].kid[j]) ac[u].kid[j]=++cnt;
        u=ac[u].kid[j];
    }
    ac[u].end++;
}
void build() 
{
    queue<int> q;
    int u=0;
    for(int i=0;i<26;i++)
    {
        if(ac[u].kid[i]) ac[ac[u].kid[i]].fail=0,q.push(ac[u].kid[i]);;
        
    }
    while(!q.empty())
    {
        u=q.front();
        q.pop();
        for(int i=0;i<26;i++)
        {
            if(ac[u].kid[i])
            {
                ac[ac[u].kid[i]].fail=ac[ac[u].fail].kid[i];
                q.push(ac[u].kid[i]);
            }
            else ac[u].kid[i]=ac[ac[u].fail].kid[i];
        }
    }
    return;
}
int countt(char *s)
{
    int len=strlen(s),u=0;
    for(int i=0;i<len;i++)
    {
        u=ac[u].kid[s[i]-'a'];
        for(int k=u;k&&ac[k].end!=-1;k=ac[k].fail)
        {
            ans+=ac[k].end;
            ac[k].end=-1;
        }
    }   
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",ch);
        add(ch);
    }
    build();
    scanf("%s",ch);
    printf("%d\n",countt(ch));
    return 0;
} 

还有两道板题(真多),先咕咕吧,等我慢慢更。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!