zoukankan      html  css  js  c++  java
  • 浅谈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
    ",len-f[1]);
    }
    
  • 相关阅读:
    电脑快捷键大全
    js实现页面跳转
    List转换为字符串方法
    Bootstrap4显示和隐藏元素
    反向代理和正向代理区别
    springboot系列一:工作环境无法联网下快速搭建boot项目
    英语故事系列:冠状病毒传播或导致2020首季度全球经济出现萎缩
    BBS网站的制作
    Flask-SQLAlchemy数据库操作
    step-by-step install Nginx反向代理服务器(Ubuntu 18.04 LTS)(转)
  • 原文地址:https://www.cnblogs.com/Chandery/p/11332808.html
Copyright © 2011-2022 走看看