【模板】AC 自动机

link

许多年之前,当学完kmp之后就一直想学它的进阶版,也就是AC自动机。但一直拖,一直拖,于是乎就拖到了今天,由于昨天考试要用今天现学了这个知识点,完成了我那跨越了一年半(我们去年1月学的kmp)的心愿。

题解区有句话说得很好,AC自动机等于Trie树加kmp,或者说树上的kmp,再或者是用kmp的思想建一张trie图。但不论如何,建立一棵Trie树都是很有必要的。

inline void insert(){
	scanf("%s",w);int len=strlen(w);
	for(int x=0,i=0;i<len;i++){
		int now=w[i]-'a';
		if(!a[x].next[now])a[x].next[now]=++cnt;
		x=a[x].next[now];
		if(i+1==len)a[x].num++;
	}
}

然后就是对最重要的fail指针的处理。按照kmp的思想,也就是利用已有的匹配来简化现在的流程。放在Trie树上就是说,假如x有一个孩子叫y,x的fail叫ff,那么当ff有x到y一样的边时,y的fail一定是ff的这个孩子。但假如ff没有这个孩子呢?也把fail设立为这个孩子,原因等会说。但假如x的一些孩子轮空了呢?next数组也不能闲着,它的含义是假如我们有到x的串,在后面再拼一个字母后的它的fail。那么这时的指针应该指向哪里?按照之前的思路应该指向ff的一个孩子。但假如ff没有这个孩子呢?那么按照定义ff的这个孩子也指向了一个fail,这么一直下去总会找到合法点的。要注意的是由于x的孩子的fail是由ff更新过来的,所以ff应该先求;而显然ff的深度比x小,那么采用广搜的办法就可以保证求解顺序。

queue<int>q;
void pre(){
	for(int i=0;i<26;i++){
		if(a[0].next[i])q.push(a[0].next[i]);
	}
	while(!q.empty()){
		int now=q.front();q.pop();
		for(int i=0;i<26;i++){
			int ff=a[now].fail;
			if(a[now].next[i]){
				a[a[now].next[i]].fail=a[ff].next[i];
				q.push(a[now].next[i]);
			}
			else a[now].next[i]=a[ff].next[i];
		}
	}
}

求解嘛可以在图上跑,就想象它是一个普通Trie树,但把它当成Trie树来做就会漏掉那些起始点不在起点的子串。而fail指针串起来的相当于就是一个后缀集合,所以每跑到一个点就顺着fail指针串,更新每一个点的值即可。这道题有个小剪枝,也就是说更新过的fail链会被掐断,毕竟不会访问第二次了(更严谨的说是再访问不会影响最后的答案)。

void solve(){
	scanf("%s",w);int len=strlen(w);
	int ans=0,x=0;
	for(int i=0;i<len;i++){
		x=a[x].next[w[i]-'a'];
		for(int y=x;y&&a[y].num>=0;y=a[y].fail){
			ans+=a[y].num;a[y].num=-1;
		}
	}
	printf("%d",ans);
}

有另外一道板子 【模板】AC 自动机(加强版) 原理是相同的。

posted @ 2022-06-19 11:54  Feyn618  阅读(2)  评论(0编辑  收藏  举报