AC自动机
前置芝士:Trie 树,KMP算法。
概述
AC 自动机是建立在 Trie 树上的自动机,一般拥有两个步骤:构建 Trie 与构造失配指针。它可以解决多模式串匹配问题。
建立
首先,将模式串插入到 Trie 树中。然后,考虑字典树中的一个节点 \(u\),它的父节点为 \(fa\),\(fa\) 通过字符 \(c\) 的边指向 \(u\)。令上面一句话表示为 \(trie(fa,c)=u\),其失配指针指向的节点表示为 \(fail(u)\)。假设深度小于 \(u\) 的失配指针都已经求得,则分三类情况:
- 若存在节点 \(v\) 使得 \(trie(fail(fa),c)=v\),则 \(fail(u)=v\)。
- 若上述前提不存在,则不断跳失配指针。即第一次跳到 \(trie(fail(fail(fa)),c)\),第二次跳到 \(trie(fail(fail(fail(fa))),c)\),直到跳到根节点。
- 若上述过程中未出现存在的节点,则 \(fail(u)\) 为根节点。
至此,失配指针的搭建完成。
例题一
P3808 AC 自动机(简单版)
给定 \(n\) 个模式串 \(s_i\) 和一个文本串 \(t\),求有多少个不同的模式串在文本串里出现过。
解法
AC 自动机裸题,非常适合 AC 自动机入门。代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+10;
struct AC//自动机结构体
{
int fail,ch[26],end;
}t[MAXN];
int tot=0,n;
int id(char c)
{
return c-'a';
}
void insert(string s)//Trie 构建
{
int now=0;
for(int i=0;i<s.size();i++)
{
if(!t[now].ch[id(s[i])]) t[now].ch[id(s[i])]=++tot;
now=t[now].ch[id(s[i])];
}
t[now].end++;
}
void get()//构建 fali
{
queue<int> q;
for(int i=0;i<26;i++)
if(t[0].ch[i])
{
t[t[0].ch[i]].fail=0;
q.push(t[0].ch[i]);
}
while(!q.empty())//bfs
{
int tmp=q.front();
q.pop();
for(int i=0;i<26;i++)
{
if(t[tmp].ch[i])
{
t[t[tmp].ch[i]].fail=t[t[tmp].fail].ch[i];
q.push(t[tmp].ch[i]);
}
else t[tmp].ch[i]=t[t[tmp].fail].ch[i];//子结点指向fail的子结点
}
}
}
int query(string s)//AC 自动机匹配
{
int now=0,res=0;
for(int i=0;i<s.size();i++)
{
now=t[now].ch[id(s[i])];
for(int j=now;j&&t[j].end!=-1;j=t[j].fail)
{
res+=t[j].end;
t[j].end=-1;
}
}
return res;
}
int main( )
{
cin>>n;
for(int i=1;i<=n;i++)
{
string s;
cin>>s;
insert(s);
}
get();
string s;
cin>>s;
cout<<query(s);
}
例题二
P5357 【模板】AC 自动机
给定文本串 \(S\) 与 \(n\) 个模式串 \(T_i\),求每个模式串在文本串中的出现次数。
\(1\le n\le 2\times 10^5,\sum\mid T_i\mid\le 2\times 10^5,\mid S\mid\le 2\times 10^6\)。
解法
多模式串匹配,考虑 AC 自动机。但是一个 AC 自动机莽过去就会 TLE,考虑优化。
发现 AC 自动机上节点入度多而出度为 \(1\),故考虑每个点只访问一次。如此一来容易想到拓补排序。但由于出度为 \(1\),所以无需建图,直接跳即可。另外,代码中采用的是建图的方法。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;
string s[MAXN],T;
int fail[MAXN],ch[MAXN][26],tot,id[MAXN],sum[MAXN];
vector<int> G[MAXN];
int insert(string s)
{
int tmp=0;
for(auto &c:s)
{
int i=c-'a';
if(!ch[tmp][i]) ch[tmp][i]=++tot;
tmp=ch[tmp][i];
}
return tmp;
}
void bfs()//建立AC自动机
{
queue<int> q;
for(int i=0;i<26;i++) if(ch[0][i]) q.push(ch[0][i]);
while(!q.empty())
{
int tmp=q.front();
q.pop();
for(int i=0;i<26;i++)
{
if(ch[tmp][i])
{
fail[ch[tmp][i]]=ch[fail[tmp]][i];
q.push(ch[tmp][i]);
}
else ch[tmp][i]=ch[fail[tmp]][i];
}
}
}
void dfs(int x)
{
for(auto &i:G[x])
{
dfs(i);
sum[x]+=sum[i];
}
}
int main( )
{
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>s[i];
id[i]=insert(s[i]);//建树
}
bfs();
cin>>T;
for(int i=1;i<=tot;i++) G[fail[i]].push_back(i);
int tmp=0;
for(auto &c:T)
{
int i=c-'a';
tmp=ch[tmp][i];
sum[tmp]++;
}
dfs(0);//求解
for(int i=1;i<=n;i++) cout<<sum[id[i]]<<'\n';
}
持续更新ing……

浙公网安备 33010602011771号