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

Posted chandery

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈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 Moonint 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]);

以上是关于浅谈Aho-Corasick automaton(AC自动机)的主要内容,如果未能解决你的问题,请参考以下文章

aho-corasick automaton (AC自动机)的理解

aho-corasick automaton (AC自动机)的理解

aho-corasick automaton (AC自动机)的理解

浅谈Suffix Automaton(后缀自动机)

用于反恶意软件代码的类似 Aho-Corasick 的算法

poj 3150Cellular Automaton 矩阵