AC自动机

模板题传送门

百度百科传送门

AC自动机分为三个部分:

  1. 构造trie树

  2. 构造fail指针

  3. 模式匹配

1.构造trie树

在trie树上,从根节点开始,到某一叶子节点的路径即为一个字符串。

每个节点有n个出度,代表字符串每一位有n种情况。

如,对于二进制数,n=2 ;对于小写字母字符串,n=26 。

因此,构建trie树,只需从根节点开始,沿着字符串向下移动,并不断补充新节点即可。

最后在叶子节点上进行相应的处理即可(具体处理方法根据题意)。

以二进制为例,trie树处理结果如图:

trie

代码:

void build_trie(char x[]){
	int len=strlen(x),p=0;
	for(int i=0;i<len;++i){
		int t=x[i]-'a';
		if(!pre[p][t])pre[p][t]=++tot;
		p=pre[p][t];
	}
	++val[p];
}

2.构造fail指针

KMP只支持 单串与单串 之间的比较,而AC自动机通过添加fail指针,记录最大相同前缀后缀,满足 单串与多串 的比较。

对于某一节点,其子节点的fail为其fail的相同子节点。

特别的,根节点的子节点的fail为根节点。

建立了fail指针后,当某个子节点不存在时,就可以顺着fail往上跳,保证连贯性。

以二进制为例,fail指针处理结果如图:

fail

代码:

void build_AC(){
	for(int i=0;i<26;++i)
		if(pre[0][i])q.push(pre[0][i]);
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int i=0;i<26;++i){
			if(pre[x][i]){
				q.push(pre[x][i]);
				fail[pre[x][i]]=pre[fail[x]][i];
			}
			else pre[x][i]=pre[fail[x]][i];
		}
	}
}

————————————————————

3.模式匹配

准备工作做完后,接下来开始匹配。

同样是顺着字符串往下走,与第一步不同的是,这次不能建立新节点,同时要关注fail。

因为fail记录的是最大相同前缀后缀,则fail的fail记录的是所有的相同前缀后缀。

因此,对于一个节点,只要一直往上fail,就可以访问到所有可能存在的答案。

代码:

int query_AC(char x[]){
	int len=strlen(x),p=0,res=0;
	for(int i=0;i<len;++i){
		int t=x[i]-'a';
		p=pre[p][t];
		for(int j=p;j && ~val[j];j=fail[j]){
			res+=val[j];val[j]=-1;
		}
	}
	return res;
}

完成以上三个步骤,AC自动机就建好了。

完整代码:

#include<bits/stdc++.h>
using namespace std;
const int M=1e6+5;
int n;
char s[M];
queue<int> q;
struct AC_AUTO{
	int pre[M][30],fail[M],val[M],tot;
	void build_trie(char x[]){
		int len=strlen(x),p=0;
		for(int i=0;i<len;++i){
			int t=x[i]-'a';
			if(!pre[p][t])pre[p][t]=++tot;
			p=pre[p][t];
		}
		++val[p];
	}
	void build_AC(){
		for(int i=0;i<26;++i)
			if(pre[0][i])q.push(pre[0][i]);
		while(!q.empty()){
			int x=q.front();q.pop();
			for(int i=0;i<26;++i){
				if(pre[x][i]){
					q.push(pre[x][i]);
					fail[pre[x][i]]=pre[fail[x]][i];
				}
				else pre[x][i]=pre[fail[x]][i];
			}
		}
	}
	int query_AC(char x[]){
		int len=strlen(x),p=0,res=0;
		for(int i=0;i<len;++i){
			int t=x[i]-'a';
			p=pre[p][t];
			for(int j=p;j && ~val[j];j=fail[j]){
				res+=val[j];val[j]=-1;
			}
		}
		return res;
	}
}AC;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		scanf("%s",s);
		AC.build_trie(s);
	}
	AC.build_AC();
	scanf("%s",s);
	printf("%d\n",AC.query_AC(s));
	return 0;
}

\[\mathcal{By}\quad\mathcal{Most}\ \mathcal{Handsome} \]

posted @ 2021-07-08 19:21  Most_Handsome  阅读(65)  评论(1)    收藏  举报