简易版AC自动机

为什么说是简易版?

因为复杂度大概是\(O(M*\overline N)\),而似乎还有另一种大概是\(O(M+\sum N)\)的。

不过据说比赛不会卡前一种做法,因为模式串一般不会很长。


那么步入正题。

对于\(trie\)树和\(KMP\)的预备知识就不多赘述了。

  • 下个定义

对于\(trie\)树的每个节点维护一个\(fail\)指针。

我们当然可以感性把\(fail\)指针和\(KMP\)的失配函数连接起来,并认为它在 某节点在与文本串匹配失败后 指示应该跳到哪继续匹配 的东西。

对它的稍准确定义是:节点\(i\)\(fail\)即为 \(i\)所在链\(L_1\)\(trie\)树 的 以二重子节点为头的 最长的 为其(\(L_1\))后缀的链\(L_2\) 的尾节点位置。

解释一下,我们找到这样一条链\(L_2\),满足它为\(L_1\)的后缀(比如对\(L_1\)为abcd,bcd,cd,d都是),并且要求\(L_2\)的头节点为\(trie\)种深度为2的节点。此时,\(fail\)指向\(L_2\)的尾巴节点。

特别的:

  1. 当不存在这样的链时,\(fail\)指向根节点
  2. 对于节点\(i\)不存在的儿子\(j\),我们将它的位置信息等价于节点\(i\)\(fail\)指针。(这与匹配有关)
  • 下面说说两个操作
    1.构建\(fail\)指针
    我们尽可能的用之前的状态求解当前的状态,而\(bfs\)是一种层次遍历,我们使用\(bfs\)
    当求解节点\(i\)的儿子\(j\)\(fail\)指针:
    若儿子\(j\)存在,把\(i\)\(fail\)指针所指向位置 的儿子\(j'\)(即\(j\)\(j'\)为同一个字符)的位置信息 作为\(j\)\(fail\)指针指向(当然,可能\(i\)\(fail\)指针根本不存在这样一个儿子,但是并没有影响)
    否则,直接把儿子的位置信息指向\(i\)\(fail\)的儿子\(j'\)的位置信息。

参考代码:

void ac_build_fail()
{
    for(int i=0;i<26;i++)
        if(t[0].son[i])
            q.push(t[0].son[i]);
    while(!q.empty())
    {
        int now=q.front();
        q.pop();
        for(int i=0;i<26;i++)
            if(t[now].son[i])
            {
                t[t[now].son[i]].fail=t[t[now].fail].son[i];
                q.push(t[now].son[i]);
            }
            else
                t[now].son[i]=t[t[now].fail].son[i];
    }
}

2.进行匹配。

当匹配到文本串的位置\(i\)时,假使我们已经在前边找到了它在\(trie\)树的最深合法匹配,我们遍历由这个点引发的\(fail\)指针组成的一条链,直到找到根节点,当遇到单词末尾时,执行相应的操作。

然后我们向下寻找\(i+1\)是否存在,如果不存在,用被\(fail\)指针等效了的节点位置信息跳转。

参考代码:

void match()
{
    int len=strlen(message),now=0;
    for(int i=0;i<len;i++)
    {
        now=t[now].son[message[i]-'a'];
        for(int j=now;j;j=t[j].fail)
            //some operations
    }
}

模板题:洛谷P3796 【模板】AC自动机(加强版)

题目描述

\(N\)个由小写字母组成的模式串以及一个文本串\(T\)。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串\(T\)中出现的次数最多。

输入输出格式

输入格式:

输入含多组数据。

每组数据的第一行为一个正整数\(N\),表示共有\(N\)个模式串,\(1≤N≤150\)

接下去\(N\)行,每行一个长度小于等于\(70\)的模式串。下一行是一个长度小于等于\(10^6\)的文本串\(T\)

输入结束标志为\(N=0\)

输出格式:

对于每组数据,第一行输出模式串最多出现的次数,接下去若干行每行输出一个出现次数最多的模式串,按输入顺序排列。

参考代码:

#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
const int N=1000010;
const int M=151;
char message[N],word[M][N];
int ans[M];
int n,cnt=0;
struct node
{
    int fail,cnt;
    int son[26];
}t[12000];

void add(int k)
{
    scanf("%s",word[k]);
    int now=0,len=strlen(word[k]);
    for(int i=0;i<len;i++)
    {
        int word0=word[k][i]-'a';
        if(!t[now].son[word0])
            t[now].son[word0]=++cnt;
        now=t[now].son[word0];
    }
    t[now].cnt=k;
}

queue <int > q;
void ac_build_fail()
{
    for(int i=0;i<26;i++)
        if(t[0].son[i])
            q.push(t[0].son[i]);
    while(!q.empty())
    {
        int now=q.front();
        q.pop();
        for(int i=0;i<26;i++)
            if(t[now].son[i])
            {
                t[t[now].son[i]].fail=t[t[now].fail].son[i];
                q.push(t[now].son[i]);
            }
            else
                t[now].son[i]=t[t[now].fail].son[i];
    }
}

void match()
{
    int len=strlen(message),now=0;
    for(int i=0;i<len;i++)
    {
        now=t[now].son[message[i]-'a'];
        for(int j=now;j;j=t[j].fail)
            ans[t[j].cnt]++;
    }
}

int main()
{
    scanf("%d",&n);
    while(n)
    {
        cnt=0;
        memset(t,0,sizeof(t));
        memset(ans,0,sizeof(ans));
        for(int i=1;i<=n;i++) add(i);
        ac_build_fail();
        t[0].fail=0;
        scanf("%s",message);
        match();
        int m_max=0;
        for(int i=1;i<=n;i++)
            m_max=max(m_max,ans[i]);
        printf("%d\n",m_max);
        for(int i=1;i<=n;i++)
            if(ans[i]==m_max)
                printf("%s\n",word[i]);
        scanf("%d",&n);
    }
    return 0;
}

2018.5.22

posted @ 2018-05-22 16:28  露迭月  阅读(136)  评论(0编辑  收藏  举报