Loading

学习笔记(3)AC自动机

一句话概括,其实就是在 \(Trie\)上进行 \(Kmp\) 操作匹配字符串的一种有限状态自动机 \((DFA)\)

\(AC\)自动机上的每个节点所表示的含义与传统的 \(Trie\) 树略有不同。\(AC\)自动机上的节点表示前缀结尾为该字符的一种状态\(AC\)自动机在失配时通过找寻另一个字符串匹配的后缀继续进行匹配。通过构建“失配指针”\((fail)\) 建立字典图以在 \(O(n+m)\) 或者 \(O(n)\) 的时间内完成对字符串的匹配与统计

构建失配指针时,若 \(tr[u][i]\) 存在,则将 \(fail[tr[u][i]]\) 指向 \(tr[fail[u]][i]\);否则将 \(tr[u][i]\) 指向 \(tr[fail[u]][i]\)。这样可以避免不停的跳 \(fail\) 指针以寻找满足存在字符 \(i\) 的模式串前缀(即:跳 \(fail\) 后要继续匹配走到的状态,类似于记忆化)

在某些类型的应用下,\(AC\)自动机上的字符串答案统计可以通过打标记 \(+\) 拓扑排序\(/\)子树求和进行优化(通过 \(fail\) 指针构建的字典图可证构成 \(DAG\),于是统计每一个 \(fail\) 节点的入度,统计答案时将 \(fail[u]\) 的答案加上 \(u\) 的答案,避免重复跳 \(fail\) 指针,可以大大提高效率)

具体实现上,需要先利用给出的模式串建立字典树,然后才能进行字符串匹配

模板

\(P3808\) 【模板】AC 自动机(简单版)

纯模板,通过 \(e\) 数组记录以相应字符结尾的前缀数

#include <bits/stdc++.h>
#define N 1000005
using namespace std;
int n;
struct AC{
	int tot;
	int tr[N][26],fail[N],e[N];
	void insert(char *s){
		int u=0;
		for(int i=1;s[i];i++){
			if(!tr[u][s[i]-'a']) tr[u][s[i]-'a']=++tot;
			u=tr[u][s[i]-'a'];
		}
		++e[u];
	}
	queue<int> q;
	void build(){
		for(int i=0;i<26;i++){
			if(tr[0][i]) q.push(tr[0][i]);
		}
		while(!q.empty()){
			int u=q.front(); q.pop();
			for(int i=0;i<26;i++){
				if(tr[u][i]){
					fail[tr[u][i]]=tr[fail[u]][i];
					q.push(tr[u][i]);
				}
				else tr[u][i]=tr[fail[u]][i];
			}
		}
	}
	int query(char *t){
		int u=0, res=0;
		for(int i=1;t[i];i++){
			u=tr[u][t[i]-'a'];
			for(int j=u;j && e[j];j=fail[j]){
				res+=e[j];
				e[j]=0;
			}
		}
		return res;
	}
}DFA;
char s[N];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%s",s+1);
		DFA.insert(s);
	}
	scanf("%s",s+1);
	DFA.build();
	printf("%d",DFA.query(s));
	return 0;
}

\(P5357\) 【模板】AC 自动机(二次加强版)

拓扑排序优化字典图:

#include <bits/stdc++.h>
#define N 2000005
using namespace std;
int n;
char s[N];
struct node{
	int fail,val,flag;
	int son[26];
	node(){fail=val=flag=0; memset(son,0,sizeof(son));}
};
struct AC{
	int tot;
	node tr[N];
	int id[N],in[N],ans[N];
	void insert(char *s,int num){
		int u=0;
		for(int i=0;s[i];i++){
			if(!tr[u].son[s[i]-'a']) tr[u].son[s[i]-'a']=++tot;
			u=tr[u].son[s[i]-'a'];
		}
		if(!tr[u].flag) tr[u].flag=num;
		id[num]=tr[u].flag;
	}
	queue<int> q;
	void build(){
		for(int i=0;i<26;i++){
			if(tr[0].son[i]) q.push(tr[0].son[i]);
		}
		while(!q.empty()){
			int u=q.front(); q.pop();
			for(int i=0;i<26;i++){
				int v=tr[u].son[i];
				if(v){
					tr[v].fail=tr[tr[u].fail].son[i];
					++in[tr[v].fail];
					q.push(v);
				}
				else tr[u].son[i]=tr[tr[u].fail].son[i];
			}
		}
	}
	void query(char *t){
		int u=0;
		for(int i=0;t[i];i++){
			u=tr[u].son[t[i]-'a'];
			++tr[u].val;
		}
	}
	void topu(){
		for(int i=0;i<=tot;i++){
			if(!in[i]) q.push(i);
		}
		while(!q.empty()){
			int u=q.front(); q.pop();
			int v=tr[u].fail; --in[v];
			ans[tr[u].flag]=tr[u].val;
			tr[v].val+=tr[u].val;
			if(!in[v]) q.push(v);
		}
	}
}DFA;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%s",s);
		DFA.insert(s,i);
	}
	DFA.build();
	scanf("%s",s);
	DFA.query(s);
	DFA.topu();
	for(int i=1;i<=n;i++) printf("%d\n",DFA.ans[DFA.id[i]]);
	return 0;
}
posted @ 2024-06-02 21:11  HRcohc  阅读(8)  评论(0编辑  收藏  举报