zoukankan      html  css  js  c++  java
  • 简易版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
    ",m_max);
            for(int i=1;i<=n;i++)
                if(ans[i]==m_max)
                    printf("%s
    ",word[i]);
            scanf("%d",&n);
        }
        return 0;
    }
    

    2018.5.22

  • 相关阅读:
    javascript中的常用表单事件用法
    关于js键盘事件的例子
    对象间引用赋值及方法时引用传递
    反编译工具reflector破解方法
    使用委托(C# 编程指南)
    委托(C# 编程指南)
    浅谈线程池(下):相关试验及注意事项
    Lambda 表达式(C# 编程指南)
    浅谈线程池(中):独立线程池的作用及IO线程池
    浅谈线程池(上):线程池的作用及CLR线程池
  • 原文地址:https://www.cnblogs.com/butterflydew/p/9063229.html
Copyright © 2011-2022 走看看