浅谈Aho-Corasick automaton(AC自动机)

Aho-Corasick automaton是什么?

要学会AC自动机,我们必须知道什么是Trie,也就是字典树。Trie树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。

首先我们要知道trie,而且要知道KMP,这样就可以学AC自动机了!

其实AC自动机就是trie和KMP的结合体。主要构建trie后使用KMP的主导思想构建fail边,每次匹配与KMP相似。

下面我们看看如何构造fail边。

fail边就是类似KMP中的next数组,在失配的时候能够指向的地方。

这就是一颗trie树,那么我们应该怎么去连fail边呢?

首先我们知道root的fail边是连向自己的,而且所有与root直接相连的点fail都指向root。

然后每个点,看看自己父亲的fail边指向的位置的点是否有一个与它长的一样的儿子,如果有,那么连上,否则继续找fail边,直到root为止(注意这个过程我们用bfs实现)。所以最终连完的fail边就是这样:

这样我们只需要每一位去匹配就好了。

有人问,怎么匹配:

举个栗子:
用上面的图:

假设原串是:ahershe,这棵trie上有his,her,he,she。
我们从root开始,先查找root点有没有当前字母的儿子a,有那么指针x指到h点上,这样一直匹配;如果没有,那么就直接跳到当前点的fail边上,这样保证前面匹配的全都是相同的,直到有这样的儿子或者已经到了root并且没有这样的儿子为止。
注意每跳一个点就必须从当前点遍历一遍它的fail边直到root的边集,就是说沿着fail边跳一直到root为止,这是为了避免当前点没有被标记,但是在它fail边到达root的路径上有被标记的点。

Exanple

【GDOI2013模拟4】贴瓷砖

Description

A镇的主街是由N个小写字母构成,镇长准备在上面贴瓷砖,瓷砖一共有M种,第i种上面有Li个小写字母,瓷砖不能旋转也不能被分割开来,瓷砖只能贴在跟它身上的字母完全一样的地方,允许瓷砖重叠,并且同一种瓷砖的数量是无穷的。
问街道有多少字母(地方)不能被瓷砖覆盖。

Input

第一行输入街道长度N(1<=N<=300,000)。
第二行输入N个英文小写字母描述街道的情况。
第三行输入M(1<=M<=5000),表示瓷砖的种类。
接下来M行,每行描述一种瓷砖,长度为Li(1<=Li<=5000),全部由小写字母构成。

Output

输出有多少个地方不能被瓷砖覆盖。

Sample Input

输入1:

6

abcbab

2

cb

cbab

输入2:

4

abab

2

bac

baba

输入3:

6

abcabc

2

abca

cab

Sample Output

输出1:

2

输出2:

4

输出3:

1

Data Constraint

N(1<=N<=300,000)

Solution

我们把原字符串每一位都进行匹配,然后首先预处理出trie中每一个点对应所有fail边中最大的长度,然后匹配的时候记录下每一位中能够覆盖到的最大长度,最后用线段树维护(当然你可以直接用差分约束)。

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#define mo 1000010
using namespace std;
struct Moon{int fail,num,maxl;}point[4000010];
int son[4000010][27];
int lengt_max[300010],length[300010];
char s[300010],ch[300010];
int len,n,root,sz;
int d[mo];
int f[300010*4],tag[300010*4];
void make_trie(char s1[300010],int len1,int t,int x,int id)
{
    if(t>len1)
    {
        point[x].num=id;
        point[x].maxl=length[id];
        return;
    }
    if(!son[x][s1[t]-96])
    {
        son[x][s1[t]-96]=++sz;
        ++son[x][0];
        make_trie(s1,len1,t+1,sz,id);
    }
    else make_trie(s1,len1,t+1,son[x][s1[t]-96],id);
}
void build_fail()
{
    int i,x,k,head=0,tail=0;
    for (i=1;i<=26&&tail<son[root][0];++i)
        if(son[root][i])
        {
            d[++tail]=son[root][i];
            point[son[root][i]].fail=root;
        }
    while(head!=tail)
    {
        head=head%mo+1;
        x=d[head];
        if(!son[x][0]) continue;
        for (i=1;i<=26;++i)
            if(son[x][i])
            {
                tail=tail%mo+1;
                d[tail]=son[x][i];
                k=point[x].fail;
                while(k!=root)
                {
                    if(son[k][i]) break;
                    k=point[k].fail;
                    if(k==root&&!son[k][i]) break;
                }
                if(son[k][i]) point[son[x][i]].fail=son[k][i];
                else point[son[x][i]].fail=root;
                point[son[x][i]].maxl=max(point[son[x][i]].maxl,point[point[son[x][i]].fail].maxl);
            }
    }
}
void Check()
{
    int x=root,k;
    for (int i=1;i<=len;)
    {
        if(son[x][s[i]-96])
        {
            x=son[x][s[i]-96];
            lengt_max[i]=max(lengt_max[i],point[x].maxl);
            ++i;
        }
        else x=point[x].fail;
        if(x==root&&!son[x][s[i]-96]) ++i;
    }
}
void change(int v,int l,int r,int x,int y)
{
    if(l==x&&r==y)
    {
        tag[v]=1;
        f[v]=r-l+1;
        return;
    }
    int mid=(l+r)/2;
    if(tag[v])
    {
        tag[v*2]=1,f[v*2]=mid-l+1;
        tag[v*2+1]=1,f[v*2+1]=r-mid;
        tag[v]=0;
    }
    if(y<=mid) change(v*2,l,mid,x,y);
    else if(x>mid) change(v*2+1,mid+1,r,x,y);
    else change(v*2,l,mid,x,mid),change(v*2+1,mid+1,r,mid+1,y);
    f[v]=f[v*2]+f[v*2+1];
}
int main()
{
    scanf("%d",&len);
    scanf("%s",s+1);
    scanf("%d",&n);
    int i,j;
    root=sz=point[1].fail=1;
    for (i=1;i<=n;++i)
    {
        scanf("%s",ch+1);
        length[i]=strlen(ch+1);
        make_trie(ch,length[i],1,root,i);
    }
    build_fail();
    Check();
    for (i=1;i<=len;++i)
        if(lengt_max[i]) change(1,1,len,i-lengt_max[i]+1,i);
    printf("%d\n",len-f[1]);
}

原文地址:https://www.cnblogs.com/Chandery/p/11332808.html

时间: 2024-08-04 15:32:08

浅谈Aho-Corasick automaton(AC自动机)的相关文章

从Trie谈到AC自动机

ZJOI的SAM让我深受打击,WJZ大神怒D陈老师之T3是SAM裸题orz...我还怎么混?暂且写篇`从Trie谈到AC自动机`骗骗经验. Trie Trie是一种好玩的数据结构.它的每个结点存的是字母,因此得名`字母树`. 出一张图让大家感受下. (image powered by SaiBu NaoCu) 上面那是一棵插入了 ape,app,applicant,application,bake,ban,banana 等词的Trie.红色结点表示接受态. 显然,查找时只需顺着链照下来,插入只需

浅谈对后缀自动机的一点理解

后缀自动机入门详解及模板 后缀自动机 自动机 要想了解后缀自动机,首先得了解自动机. 例如AC自动机,AC自动机可以识别一个字符串为其所匹配的前缀. 而我们今天所介绍的后缀自动机则是识别一个字符串为自动机串的子串. 在接下来的描述中为了方便,简称\(SAM\). 暴力实现 我们知道字典树有着优良的时空复杂度,并且可以支持识别一个字符串的前缀. 如果我们将串中的所有后缀插入进字典树,那么就可以实现这个自动机的功能. 不过,由于忽视了后缀的这个性质,总点数高达\(O(n^2)\). 即使如此,字典树

浅谈后缀自动机SAM

一下是蒟蒻的个人想法,并不很严谨,仅供参考,如有缺误,敬请提出 参考资料: 陈立杰原版课件 litble 某大神 某大神 其实课件讲得最详实了 有限状态自动机 我们要学后缀自动机,我们先来了解一下自动机到底是什么.[虽说以前也学过AC自动机,只是当一个名字罢了] 有限自动机的功能是识别字符串,作用各不相同 如果自动机A能识别串s,那么A(s) = true 自动机有一个初始状态,从初始状态出发能到达多个状态.到达终止状态表示字符串识别 后缀自动机SAM 我们略去建机原理的分析和建机过程,具体原理

跳跃表,字典树(单词查找树,Trie树),后缀树,KMP算法,AC 自动机相关算法原理详细汇总

第一部分:跳跃表 本文将总结一种数据结构:跳跃表.前半部分跳跃表性质和操作的介绍直接摘自<让算法的效率跳起来--浅谈"跳跃表"的相关操作及其应用>上海市华东师范大学第二附属中学 魏冉.之后将附上跳跃表的源代码,以及本人对其的了解.难免有错误之处,希望指正,共同进步.谢谢. 跳跃表(Skip List)是1987年才诞生的一种崭新的数据结构,它在进行查找.插入.删除等操作时的期望时间复杂度均为O(logn),有着近乎替代平衡树的本领.而且最重要的一点,就是它的编程复杂度较同类

中文分词系列(二) 基于双数组Tire树的AC自动机

秉着能偷懒就偷懒的精神,关于AC自动机本来不想看的,但是HanLp的源码中用户自定义词典的识别是用的AC自动机实现的.唉-没办法,还是看看吧 AC自动机理论 Aho Corasick自动机,简称AC自动机,要学会AC自动机,我们必须知道什么是Trie,也就是字典树.Trie树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计.它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高.之

LightOJ 1427 Substring Frequency (II) (AC自动机)

题意:给定一个文本串和 n 个子串,问你子串在文本串出现的次数. 析:很明显的AC自动机,只要把先把子串进行失配处理,然后再去用文本串去匹配,在插入子串时就要标记每个串,注意串可能是相同的,这个我错了两次,最后匹配一次就OK了. 代码如下: #pragma comment(linker, "/STACK:1024000000,1024000000") #include <cstdio> #include <string> #include <cstdlib

HDU 2243 考研路茫茫――单词情结 ——(AC自动机+矩阵快速幂)

和前几天做的AC自动机类似. 思路简单但是代码200余行.. 假设solve_sub(i)表示长度为i的不含危险单词的总数. 最终答案为用总数(26^1+26^2+...+26^n)减去(solve_sub(1)+solve(2)+...+solve_sub(n)).前者构造f[i]=f[i-1]*26+26然后矩阵快速幂即可(当然也可以分治的方法).后者即构造出dp矩阵p,然后计算(p^1+p^2+...+p^n),对其分治即可. 代码如下: 1 #include <stdio.h> 2 #

hdu4787 AC自动机加分块

这题说的是 有n次操作 +w 表示读入一个字符串,?p 询问这个字符串的子串在那些模板串中有多少个, http://blog.csdn.net/qq574857122/article/details/16826631 就是说先存一部分的字符串,因为每次都要进行重新 建立这个失配指针,也就是说让适当的单词进行失配 指针重建 会达到高效,两个ac自动机,选取sqrt(100000)的时候达到相对优一点,这样我们 当第一棵树超过了 800的时候我们就将第一棵树的东西存入第二棵树这样我们可以相对减少对失

AC自动机- 自我总结

AC自动机算法总结  No.1 What's Aho-Corasick automaton? 一种多模式串匹配算法,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一. 简单的说,KMP用来匹配一个模式串:但如果现在有多个模式串需要在同一篇文章中出现,现在就需要Aho-Corasick automaton算法了. 不要天真的以为AC自动机为auto-Accept,虽然他能让你AC一些题. No.2 My Understanding About Aho-Corasick automato