AC自动机

概述

AC(Aho–Corasick)自动机是 以 Trie 的结构为基础,结合 KMP 的思想 建立的自动机,用于解决多模式匹配等任务.

AC 自动机本质上是 Trie 上的自动机.

算法步骤

  1. 将所有子串建成一颗Trie
  2. 对 Trie 树上所有的结点构造失配指针(回跳边)和转移边(便于文本串的遍历)

如何构造失配指针

定义失配指针ne,\(ne_u\) 表示 \(u\) 点所在模式串的最长后缀模式串

如图,灰边为Trie边,黄边为失配指针连边

图片

转移

可以得到回跳边和转移边的转移方程

\[\begin{cases} ne[ch[u][p]]=ch[ne[u]][p]\space (ch[u][p]\ne 0) \\ch[u][p]=ch[ne[u]][p]\space (ch[u][p]=0) \end{cases} \]

其中回跳边的转移是显然的。

转移边的转移意思就是如果文本串走一条路走到头了,就去看子串里面还有没有路可以走(这样可以保证能够遍历到所有的模式串)

按上面的图举个例子:假如文本串为hershe,那么当它到4号点时,因为s是hers的后缀,所有它可以继续走到8号点,相当于遍历了sh

回跳边和转移边的构建使用BFS即可

void build(){
	queue<int> q;
	for(int i=0;i<26;i++) if(ch[0][i]) q.push(ch[0][i]);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<26;i++)
			if(ch[u][i]) ne[ch[u][i]]=ch[ne[u]][i],q.push(ch[u][i]);
			else ch[u][i]=ch[ne[u]][i]; 
	}
}

匹配

在建造回跳边和转移边后,我们直接在Trie上遍历文本串,对于遍历到的节点,用失配指针就可以遍历当前遍历模式串的子串

int find(char *s){
	int ans=0;
	for(int k=0,i=0;s[k];k++){
		i=ch[i][int(s[k]-'a')];
		for(int j=i;j&&~cnt[j];j=ne[j]){
				ans+=cnt[j];
				cnt[j]=-1;
		}
	}
	return ans;
}

失配指针的启发

每次用失配指针回跳,时间上是难以接受的

观察上图我们可以发现失配指针恰好是树状的

也就是说我们可以直接用树上dp,重链剖分等方法来优化复杂度

下面以两道例题来说明(下文树上的子串都指子串在Trie树上的尾节点)

例一

如果一个串出现过,那么它的子串一定出现过

由于失配树上子串是原串的祖先,那么可以直接dfs:\({ans}_u=cnt_u+\sum_{v\in son(u)}{ans}_v\)

复杂度 \(O(26\space n+m)\)

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int ch[N][26],ne[N],sum[N],n,tot;
int ed[N];
string t;
vector<int> e[N];
void insert(string s,int x){
	int len=s.length(),u=0;
	for(int i=0;i<len;u=ch[u][s[i]-'a'],i++) 
		if(!ch[u][s[i]-'a']) 
			ch[u][s[i]-'a']=++tot;
	ed[x]=u;
}
void build(){
	queue<int> q;
	for(int i=0;i<26;i++) if(ch[0][i]) q.push(ch[0][i]);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<26;i++)
			if(ch[u][i]) ne[ch[u][i]]=ch[ne[u]][i],q.push(ch[u][i]);
			else ch[u][i]=ch[ne[u]][i]; 
	}
}
void dfs(int u){for(auto v:e[u]){dfs(v);sum[u]+=sum[v];}}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){cin>>t;insert(t,i);}
	build();
	cin>>t;int len=t.length();
	for(int i=0,u=0;i<len;u=ch[u][t[i]-'a'],i++) sum[ch[u][t[i]-'a']]++;
	for(int i=1;i<=tot;i++) e[ne[i]].push_back(i);
	dfs(0);
	for(int i=1;i<=n;i++) 
		cout<<sum[ed[i]]<<"\n";
	return 0;
}

例二

一个串与它的子串在失配指针树上相当于一条到根节点的链,因此重链剖分加线段树维护即可,注意可能有重串,要用multimap来记录

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define ls (p<<1)
#define rs ((p<<1)^1)
#define lson ls,l,mid
#define rson rs,mid+1,r
const int N=3e5+5;
int ch[N][26],ne[N],n,tot,dfn[N],siz[N],tim,m,son[N],top[N],pval[N];
int ed[N];
int tr[N<<2];
multiset<int> val[N];
string t;
vector<int> e[N];
void insert(string s,int x){
	int len=s.length(),u=0;
	for(int i=0;i<len;u=ch[u][s[i]-'a'],i++) 
		if(!ch[u][s[i]-'a']) 
			ch[u][s[i]-'a']=++tot;
	ed[x]=u;
}
void build(){
	queue<int> q;
	for(int i=0;i<26;i++) if(ch[0][i]) q.push(ch[0][i]);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<26;i++)
			if(ch[u][i]) ne[ch[u][i]]=ch[ne[u]][i],q.push(ch[u][i]);
			else ch[u][i]=ch[ne[u]][i]; 
	}
}
void dfs1(int u){
	siz[u]=1;
	for(auto v:e[u]){
		dfs1(v);siz[u]+=siz[v];
		if(!son[u]||siz[v]>siz[son[u]]) son[u]=v;
	}
}
void dfs2(int u,int tp){
	dfn[u]=++tim,top[u]=tp;
	if(son[u]) dfs2(son[u],tp);
	for(auto v:e[u]){
		if(son[u]==v) continue;
		dfs2(v,v);
	}
}
void pushup(int p){tr[p]=max(tr[ls],tr[rs]);}
void build(int p,int l,int r){
	tr[p]=-1;
	if(l==r) return;
	build(lson),build(rson);
}
void update(int p,int l,int r,int x,int v){
	if(l==r){tr[p]=v;return;}
	if(x<=mid) update(lson,x,v);
	else update(rson,x,v);
	pushup(p);
}
int query(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R) return tr[p];
	int res=-1;
	if(L<=mid) res=max(res,query(lson,L,R));
	if(R>mid) res=max(res,query(rson,L,R));
	return res;
}
int que(int u){
	int res=-1;
	for(;u;u=ne[top[u]]){
		res=max(res,query(1,1,tot+1,dfn[top[u]],dfn[u]));
	}
	return res;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>t;insert(t,i);
		val[ed[i]].insert(0);
	}
	build();
	for(int i=1;i<=tot;i++) e[ne[i]].push_back(i);
	dfs1(0);
	dfs2(0,0);
	build(1,1,tot+1);
	for(int i=1;i<=n;i++) update(1,1,tot+1,dfn[ed[i]],0);
	while(m--){
		int op;cin>>op;
		if(op==1){
			int i,x;cin>>i>>x;
			val[ed[i]].erase(val[ed[i]].find(pval[i]));
			pval[i]=x,val[ed[i]].insert(x);
			update(1,1,tot+1,dfn[ed[i]],*prev(val[ed[i]].end()));
		}else{
			cin>>t;int res=-1,len=t.length();
			for(int i=0,u=0;i<len;u=ch[u][t[i]-'a'],i++) 
				res=max(res,que(ch[u][t[i]-'a']));
			cout<<res<<"\n";
		}
	}
	return 0;
}

posted @ 2026-10-30 14:51  Rainesxiao  阅读(3)  评论(0)    收藏  举报