学习笔记——AC自动机

AC自动机是著名的用来处理多模式串匹配的算法

本文讲自己的理解,如有不正确请各位\(dalao\)指出QAQ

一、\(\text{问题}\)

给定多个模式串和一个文本串,求多少个模式串出现在文本串中

二、\(\text{思路}\)

暴力的方法就是将每一个模式串与文本串都跑一次\(KMP\),时间复杂度\(O\)(\(mn\))

如果可以把所有模式串都打成一个串,就可以尝试直接将它与文本串跑\(KMP\),这就是AC自动机(雾)


可以发现,如果所有字符串融合为一,就可以形成一颗\(trie\),所以AC自动机是建立在\(trie\)上的,由于是树形结构,部分操作不同于\(KMP\)

三、\(\text{流程}\)

1.建树:少一点废话,多一点理解(同\(trie\)

建树如图:

2.求\(fail\)指针:

在AC自动机上\(fail\)指针的定义为:\(fail\)指向的那个点到根所形成的串和当前点向上相同长度所形成的串相同(即前缀等于后缀)。

通过画图可以发现,\(fail\)指向的点的深度一定小于自己,那么要求一个点的\(fail\),就一定要处理所有深度比它低的点,所以可以\(bfs\)分层推进

从队列中拿出一个点,处理它的儿子,如果有儿子,那么儿子的\(fail\)应该是父亲的\(fail\)对应的儿子;如果没有儿子,那么这个儿子的方向指向父亲的\(fail\)对应的儿子(相当于父亲找了个别的同名的儿子233)

3.询问文本串

\(now\)记录当前在\(trie\)上的节点编号,每次向下跳一格到对应的儿子(如果没有儿子因为上面2操作会跳到相同名字的最近的儿子),然后用替身不停跳\(fail\)(这些\(fail\)到根就是代表了可以组成的单词),加上途径的点的\(ending\)

举个栗子:er,he,hc,kher

如图

那么部分节点的\(fail\)用红色的线表示

关于更新儿子:当到进行到第二个e的时候,我们看到它没有r这个儿子,所以把它的\(fail\)(即第一个e)所对应的那个儿子r作为自己的儿子,即蓝色线的含义

这样做的话,更新第二个r的时候,它应该指向它的父亲(第三个e)对应的\(fail\)(第二个e)的r儿子,即最下面那条红色线由上面那一条更新来

关于询问文本串:keher,数字表示访问顺序

\(Code\)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define N 500005
using namespace std;
int T,n;

namespace AC
{
	int nxt[N][27],ending[N],fail[N],ndsum;
	void init()
	{
		memset(nxt,0,sizeof(nxt));
		memset(ending,0,sizeof(ending));
		ndsum=0;
	}
	void ad(char a[])//建树同trie 
    {
        int now=0,len=strlen(a);
        for(int i=0;i<len;++i)
        {
            int v=a[i]-'a'+1;
            if(!nxt[now][v]) nxt[now][v]=++ndsum;
            now=nxt[now][v];
        }
        ending[now]++;
    }
	void getfail()
	{
		queue<int> q;
		for(int i=1;i<=26;++i) if(nxt[0][i]) fail[nxt[0][i]]=0,q.push(nxt[0][i]);
		while(!q.empty())
		{
			int u=q.front();q.pop();
			for(int i=1;i<=26;++i) 
			if(nxt[u][i]) fail[nxt[u][i]]=nxt[fail[u]][i],q.push(nxt[u][i]);
			else nxt[u][i]=nxt[fail[u]][i];
		}
	}
	int query(char a[])
	{
		int now=0,ans=0,len=strlen(a);
		for(int i=0;i<len;++i)
		{
			int k=a[i]-'a'+1;
			now=nxt[now][k];
			for(int t=now;t&&ending[t]!=-1;t=fail[t]) ans+=ending[t],ending[t]=-1;
		}
		return ans;
	}
}

int main()
{
	char c[1000001];
	AC::init();
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%s",&c);
		AC::ad(c);
	}
	AC::getfail();
	scanf("%s",&c);
	printf("%d\n",AC::query(c));
	return 0;
}

感谢yyr大佬的倾情♂讲解

posted @ 2019-02-19 11:09  擅长平地摔的艾拉酱  阅读(142)  评论(0编辑  收藏  举报
/*取消选中*/