这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战.
多模式匹配问题:给定若干个模式串,每次询问给定一个文本串,问各模式串在文本串中各出现了多少次。
AC自动机最典型的应用就是求解多模式匹配问题,此外通过与DP组合可以求解其它问题。
前置知识:
- Trie树:又叫字典树,每条边上都有一个字母。
- fail指针:fail[i]表示与以i节点为结尾的串的后缀有最大公共长度的前缀的结尾编号,fail的思想来自于kmp算法。
构建: 首先将各模式串组成一棵Trie树,标记所有终结点。然后在Trie树上按bfs序访问每个节点,操作如下:
- 建立bfs用的队列,将根节点所有存在子节点的fail设置成根节点,然后把他们加入队列中。
- 从队列里取出当前节点,遍历26(字符集大小,默认26)个子节点,如果存在,则将子节点的fail设置成当前节点fail的对应子节点,并将子节点加入队列中。
- 如果不存在,则将这条边连向当前节点fail的对应子节点。
- 队列非空时,返回2.
构建完成之后,由于第三步的存在,Trie树中的每个节点都会引出26条有向边,而且可能指向高层节点,现在的Trie结构由就Trie树变成了Trie图。
构建过程中除了建立Trie图之外,还求出了每个节点的fail,把所有的fail指针拿出来就是一棵fail树。
查询: Trie图和fail树就是AC自动机的核心结构。
fail树上根到每个节点所表示的字符串,都是根到它子节点所表示的字符串的后缀。 查询时类比Trie树查询,直接在Trie图上走就可以,可以时刻保证位置的正确性。
在Trie图上走时,每到一个节点,需要遍历fail树上从它到根的路径,每有一个带终结标记的节点,就表示匹配到了这个字符串一次。
优化: 针对多模式匹配查询的优化有两个:
- 建立fail树时,找到每个节点的祖先中最近的带终结标记的节点,然后直接连一条边,称为后缀链接,或者last指针,找到每个节点的last边后就建立了last树。匹配时,直接在last树上而非fail树上统计答案,可以有效降低时间复杂度。
- 在last树上统计答案的过程相当于每次给定一个节点,将根到它路径上所有的点权加一,最后只输出一个答案,可以使用树上差分来将复杂度优化成线性。
代码:
//fail[i]为与以i节点为结尾的串的后缀有最大公共长度的前缀的结尾编号
//注意字符集
struct Aho_Corasick_Automaton
{
int ch[M][26]={}, end[M]={}, sz=0; //Trie树: end表示以i为结尾的串编号
int fail[M]={}, last[M]={}; //AC自动机: 失配数组, 后缀连接
int ans[M]={}; //答案存储
int init(int id = 0)
{
memset(ch[id], 0, sizeof(ch[0]));
fail[id] = last[id] = end[id] = 0;
return sz = id;
}
// 向Trie树中尝试插入一个模式串, 返回插入后的编号
int insert(int id, const char *s)
{
int u = 0;
for(int i = 0; s[i]; i++)
{
int &v = ch[u][s[i] - 'a'];
if(!v) v=init(sz+1);
u = v;
}
if(!end[u]) end[u] = id;
return end[u];
}
// 构建AC自动机
void build()
{
queue<int> q;
for(int v:ch[0]) if(v)
fail[v] = 0, q.push(v);
while(!q.empty())
{
int u = q.front(); q.pop();
for(int i = 0; i < 26; i++)
{
int &v = ch[u][i];
if(v)
{
q.push(v);
fail[v] = ch[fail[u]][i];
last[v] = end[fail[v]] ? fail[v] : last[fail[v]]; //后缀链接
}
else v = ch[fail[u]][i]; //建立trie图
}
}
}
// 查询一个文本串中各模式串出现了几次, 保存在ans中
void query(const char *s)
{
memset(ans, 0, sizeof(ans));
int now = 0;
for(int i = 0; s[i]; i++)
{
now = ch[now][s[i] - 'a'];
int id = end[now] ? now : last[now];
while(id) ++ans[end[id]], id = last[id];
}
}
}AC;
例题 本质上都是模板题,问法各有不同,可以多熟悉一下。
- luogu P3808 【模板】 AC自动机(简单版)
- luogu P5357 【模板】 AC自动机(二次加强版)
- luogu P3796 【模板】 AC自动机(加强版)
- HDU 2222
- HDU 2896
- HDU 3065
本文也发表于我的 csdn 博客中。