【Coel.解题报告】【阿霍-柯拉西克自动机!】AC自动机(简单版)

今天在鸽子大佬的帮助之下学会了AC自动机的基本操作,在此感谢!!!

你可以在史前找到我的血 2021/11/27 18:32:40
首先要构造一颗trie

你可以在史前找到我的血 2021/11/27 18:33:23
然后构造fail指针 使当前字符失配时跳转到具有最长公共前后缀的字符继续匹配

你可以在史前找到我的血 2021/11/27 18:33:33
就像KMP里面一样

你可以在史前找到我的血 2021/11/27 18:33:36
失配指针

你可以在史前找到我的血 2021/11/27 18:34:07
AC自动机当前字符匹配失败的话 就用fail指针跳转

你可以在史前找到我的血 2021/11/27 18:34:54
跳转后的串的前缀 必定是跳转前的模式串的后缀

你可以在史前找到我的血 2021/11/27 18:35:12
而且跳转新位置的深度 一定小于跳转之前的节点

℃oel 2021/11/27 18:35:25
那fail怎么实现

你可以在史前找到我的血 2021/11/27 18:35:37
所以这个fail指针 一般是利用bfs在trie上面求解

你可以在史前找到我的血 2021/11/27 18:35:58
你可以在史前找到我的血  
首先要构造一颗trie
首先说这个trie

你可以在史前找到我的血 2021/11/27 18:36:05
需要三个指针

你可以在史前找到我的血 2021/11/27 18:36:12
p p->fail temp

你可以在史前找到我的血 2021/11/27 18:36:47
指针p指向当前匹配的字符串 如果p指向root 那就是当前匹配字符序列为空

你可以在史前找到我的血 2021/11/27 18:36:56
(root是trie的入口

你可以在史前找到我的血 2021/11/27 18:37:14
p->fail p的失败指针 指向和字符p相同的节点

你可以在史前找到我的血 2021/11/27 18:37:39
temp 在建立fail指针时有寻找与p字符匹配的节点作用

你可以在史前找到我的血 2021/11/27 18:37:55
对于trie树内的任意节点

你可以在史前找到我的血 2021/11/27 18:38:09
对应一个序列s[1...m]

你可以在史前找到我的血 2021/11/27 18:38:22
这个时候呢 p指向字符s[m]

你可以在史前找到我的血 2021/11/27 18:38:30
如果在下一个字符失配

你可以在史前找到我的血 2021/11/27 18:38:51
即p->next[s[m+1]] ==NLL

你可以在史前找到我的血 2021/11/27 18:39:06
则由失配指针跳转到p->fail

你可以在史前找到我的血 2021/11/27 18:39:17
对应的序列是s[i...m]

你可以在史前找到我的血 2021/11/27 18:39:34
如果继续失配 就依次跳转到序列空或出现匹配

你可以在史前找到我的血 2021/11/27 18:40:01
这个过程中p的值一直变化 但p对应节点的字符没有改变

你可以在史前找到我的血 2021/11/27 18:40:19
可以求得序列s为最长公共后缀

你可以在史前找到我的血 2021/11/27 18:40:29
由于这个序列是从root开始到某一节点,则说明这个序列有可能是某些序列的前缀

你可以在史前找到我的血 2021/11/27 18:41:47
回到p指针上

你可以在史前找到我的血 2021/11/27 18:41:59
如果p指针在某一字符s[m+1]失配

你可以在史前找到我的血 2021/11/27 18:42:13
则说明没有单词s[1...m+1]存在

你可以在史前找到我的血 2021/11/27 18:42:30
这个时候 如果失配指针指向root

你可以在史前找到我的血 2021/11/27 18:42:31
则说明当前序列的任意后缀不会是某个单词的前缀

你可以在史前找到我的血 2021/11/27 18:42:53
如果不指向root

你可以在史前找到我的血 2021/11/27 18:42:59
则说明是

你可以在史前找到我的血 2021/11/27 18:43:14
所以跳转到p的失配指针

你可以在史前找到我的血 2021/11/27 18:44:07
以s[i...m]为前缀继续匹配s[m+1]

你可以在史前找到我的血 2021/11/27 18:45:34
对于已经得到的序列s[1...m]由于s[i...m]可能是某个词的后缀 s[1...j]可能是某个词的前缀  所以s[1...m]可能存在词

你可以在史前找到我的血 2021/11/27 18:46:05
但是这个时候 p已经指向已匹配的字符

你可以在史前找到我的血 2021/11/27 18:46:16
所以令temp = p

你可以在史前找到我的血 2021/11/27 18:46:35
之后依次测试s[1...m] s[i...m]是否为完整词

你可以在史前找到我的血 2021/11/27 18:48:06
对于失配指针 可以用和KMP相似的思路

你可以在史前找到我的血 2021/11/27 18:50:03
root入队 第一次循环处理和root相连的

 

P3808 【模板】AC自动机(简单版)

题目传送门

LOJ也有一道模板题,在此附上(多组数据注意)

#10057. 「一本通 2.4 例 1」Keywords Search

AC自动机由贝尔实验室的两位科学家Alfred V. Aho和Margaret J.Corasick于1975年发明(和KMP的发明时间十分相近),所以这个算法的名字以这两位大佬命名(Aho-Corasick Automaton)。

这个算法建立在一棵字典树上,同时利用一个失配函数(类似KMP的fail)简化搜索路径,所以又被称为树上KMP。

实现过程如下:

  1. 把所有的模式串挂在字典树上(这一点和普通字典树一样)
  2. 通过深度优先搜索把所有的失配指针算出来
  3. 将文本串进行匹配

代码如下:

#include<cstdio>
#include<queue>
#include<cstring>
#define maxn 1000050
#define maxc 26
#define root 1
using namespace std;
int n,cnt=1;
char s[maxn];
queue<int>Q;
struct Aho_Corasick_Automaton
{
    int leaf[maxc],tot,fail;
}trie[maxn];
inline void insert(char *s)//字典树的插字符串方式
{
    int u=1,len=strlen(s);
    for(register int i=0;i<len;i++)
    {
        int c=s[i]-'a';
        if(!trie[u].leaf[c])
            trie[u].leaf[c]=++cnt;
        u=trie[u].leaf[c];
    }
    trie[u].tot++;
}
inline void getfail()//失配函数预处理
{
    for(register int i=0;i<maxc;i++)
        trie[0].leaf[i]=1;
    Q.push(root);
    trie[root].fail=0;
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop();
        for(register int i=0;i<maxc;i++)
        {
            int c=trie[u].leaf[i],fail=trie[u].fail;
            if(!c)
                trie[u].leaf[i]=trie[fail].leaf[i];
            else
            {
                trie[c].fail=trie[fail].leaf[i];
                Q.push(c);
            }
        }
    }
}
inline int query(char *s)//进行匹配
{
    int u=root,ans=0,len=strlen(s);
    for(register int i=0;i<len;i++)
    {
        int c=s[i]-'a',nxt=trie[u].leaf[c];
        while(nxt>1&&trie[nxt].tot!=-1)
        {
            ans+=trie[nxt].tot;
            trie[nxt].tot=-1;
            nxt=trie[nxt].fail;
        }
        u=trie[u].leaf[c];
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(register int i=1;i<=n;i++)
    {
        scanf("%s",s);
        insert(s);
    }
    getfail();
    scanf("%s",s);
    printf("%d",query(s));
    return 0;
}
posted @ 2021-11-27 20:58  秋泉こあい  阅读(57)  评论(0编辑  收藏  举报