AC自动机
概述
AC(Aho–Corasick)自动机是 以 Trie 的结构为基础,结合 KMP 的思想 建立的自动机,用于解决多模式匹配等任务.
AC 自动机本质上是 Trie 上的自动机.
算法步骤
- 将所有子串建成一颗Trie
- 对 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;
}

浙公网安备 33010602011771号