「学习笔记」AC自动机

Aho-Corasick automaton


updata on 8.3.2020:重画了前缀树的图。
虽然还是很丑,但是至少正确性有保证。


前言

我还不会KMP
AC自动机是个好东西,虽然只有少数情况下能够真正地达到它名字所描述的效果
这是一种多模式匹配算法,用于在文本串中搜索模式串
学习AC自动机,我个人认为只需要学会Trie树就行了,当然如果会KMP的话就更好,因为AC自动机可以简单理解为 Trie+KMP。


时间复杂度

总的时间复杂度约为 O((n+m)×len)O((n+m)\times len),其中 n 为模式串个数, m 为模式串的最大长度, len为文本串长度。
构建Trie树: O(n×len)O(n\times len); 设置fail指针: O(n×len)O(n\times len); 模式匹配: O(m×len)O(m\times len)


步骤

   1.trie\ \ \ \mathtt {1.建trie树}
见【字典树笔记】。

   2.fail\ \ \ \mathtt {2.设置fail指针}^*
一个点的fail指针,即失配指针,其指向的位置的深度是肯定小于该点的深度的,
所以第一层(此处指树根的下一层)的结点的失配指针显然只能指向树根。

对于其他的任意一个结点 i,若其父亲的失配指针指向的结点的某一子结点 j 与其值相等,则结点 i 的失配指针指向 j;若不存在这种情况,则结点 i 的失配指针指向根结点。

如下图(红线表示失配指针)
在这里插入图片描述

求得所有失配指针后的图如下
在这里插入图片描述

    3.\ \ \ \ \mathtt {3.查询}
跳fail,并打上标记去重。


\small{^*为算法核心部分}


code:\large\texttt {code:}

#include <bits/stdc++.h>
#define tag -0x3f3f3f
using namespace std;

const int maxn=1e6+5;
struct node {
	int son[26] , fail;
} tree[maxn];
int cnt=1;
int flag[maxn];
queue<int> que;

namespace AC_automaton {
	inline void insert(char *str) {
		int u=1 , len=strlen(str);
		
		for (register int i=0; i<len; i++) {
			int v=(int)(str[i]-'a');
			
			if (tree[u].son[v]==0) {
				tree[u].son[v]=++cnt;
			}
			
			u=tree[u].son[v];
		}
		
		flag[u]++;
	}
	
	inline void get_fail() {
		for (register int i=0; i<26; i++) {
			tree[0].son[i]=1;
		}
		que.push(1); tree[1].fail=0;
		
		while (!que.empty()) {
			int u=que.front(); que.pop();
			
			for (register int i=0; i<26; i++)  {
				int v=tree[u].son[i] , fail=tree[u].fail;
				
				if (!v) {
					tree[u].son[i]=tree[fail].son[i];
				}
				else {
					tree[v].fail=tree[fail].son[i];
					que.push(v);
				}
			}
		}
	}
	
	inline int query(char *str) {
		int u=1 , ans=0 , len=strlen(str);
		
		for (register int i=0; i<len; i++) {
			int v=(int)(str[i]-'a') , pos=tree[u].son[v];
			
			while (pos>1 && flag[pos]!=tag) {
				ans+=flag[pos];
				flag[pos]=tag;
				pos=tree[pos].fail;
			}
			
			u=tree[u].son[v];
		}
		
		return ans;
	}
}
using namespace AC_automaton;

template <typename conv>
inline conv read(conv &x) {
	char ch=getchar(); bool flag=0;
	
	if (ch=='-') {
		flag=1;
	}
	
	while (ch<'0' || ch>'9') {
		ch=getchar();
	}
	
	for (x=0; ch>='0' && ch<='9'; ch=getchar()) {
		x=(x<<1)+(x<<3)+ch-'0';
	}
	
	if (flag) {
		x=-x;
	}
}

template <typename conv>
inline conv write(conv x) {
	if (x<0) {
		putchar('-');
		x=-x;
	}
	
	if (x>9) {
		write(x/10);
	}
	
	putchar(x%10+'0');
}

int n;
char str[maxn];
int main() {
	read(n);
	for (register int i=1; i<=n; i++) {
		scanf("%s" , str);
		insert(str);
	}
	
	get_fail();
	
	scanf("%s" , str);
	
	write(query(str));
	
	return 0;
}

Thats all.\mathrm{That's\ all.}

posted @ 2020-07-24 21:41  willbe233  阅读(71)  评论(0)    收藏  举报