AC自动机 就是 t r i e + K M P \tt trie + KMP trie+KMP
前置知识
失配指针
算法用途
多模式串匹配
算法复杂度
时间
模式串数量 n \tt n n,平均长度 l e n \tt len len
文本串长度 m \tt m m
构建 t r i e \tt trie trie 树: O ( n × l e n ) \tt O(n \times len) O(n×len)
构建失败指针: O ( n × l e n ) \tt O(n \times len) O(n×len)
匹配: O ( 2 × m ) \tt O(2 \times m) O(2×m)
总时间: O ( n × l e n + m ) \tt O(n \times len + m) O(n×len+m)
空间
与 t r i e \tt trie trie 树一样
算法实现
创建 t r i e \tt trie trie 树
创建失配指针
在 K M P \tt KMP KMP 里,当字符串的前后缀相同时,我们就构建失配指针。
所以我们在 t r i e \tt trie trie 树上构建失配指针就是当两个字符串的前后缀相同就行了。
这里采用递推思想,这个节点的失配指针肯定指向这个节点的父亲的失配指针的儿子
f a i l [ i ] = s o n [ f a i l [ f a t h e r [ i ] ] ] [ s t r [ i ] ] \tt fail[i] = son[fail[father[i]]][str[i]] fail[i]=son[fail[father[i]]][str[i]]
初始化:深度为 2 \tt 2 2 的节点的 f a i l \tt fail fail 指针肯定指向根节点(根节点深度 1 \tt 1 1)
然后用这个办法 b f s \tt bfs bfs 整颗 t r i e \tt trie trie树就行了
算法优化
我们发现, A C \tt AC AC 自动机每次跳 f a i l \tt fail fail ,最坏情况下只能向上跳一层。
这样,我们在匹配的是后,时间复杂度是 O ( n × m ) \tt O(n\times m) O(n×m) ( n \tt n n 为文本串长度, m \tt m m 为模式串长度)
有没有办法优化呢?当然有。
我们可以先在匹配的时候用懒标记,记录下要暴力搜索的地方。
再把 f a i l \tt fail fail 指针看成边,跑一遍拓扑来计算答案就行了。
代码
A C \tt AC AC 自动机 + + + 拓扑优化
#include<iostream>
#include<queue>
using namespace std;
vector<int> a[1000010];
int son[1000010][26];
int fail[1000010];
int ans[1000010];
int num[1000010];
int flag[1000010];
int ind[1000010];
queue<int> que;
queue<int> que2;
int cnt, n;
int insert(string str, int id)//插入
{
int p = 0;
for(int i = 0; i < str.size(); i++)
{
if(son[p][str[i] - 'a'])
{
p = son[p][str[i] - 'a'];
}
else
{
son[p][str[i] - 'a'] = ++cnt;
p = cnt;
}
}
num[p]++;
a[p].push_back(id);
return 0;
}
int get_fail()//创建fail指针
{
for(int i = 0; i < 26; i++)
{
if(son[0][i])
{
que.push(son[0][i]);
ind[0]++;
}
}
while(!que.empty())
{
int u = que.front();
que.pop();
for(int i = 0; i < 26; i++)
{
if(son[u][i])
{
fail[son[u][i]] = son[fail[u]][i];
ind[son[fail[u]][i]]++;
que.push(son[u][i]);
}
else
{
son[u][i] = son[fail[u]][i];
}
}
}
return 0;
}
int find(string str)//多模匹配
{
int p = 0;
for(int i = 0; i < str.size(); i++)
{
p = son[p][str[i] - 'a'];
flag[p]++;
}
}
int topo()//拓扑排序
{
for(int i = 0; i <= cnt; i++)
{
if(!ind[i])
{
que2.push(i);
}
}
while(!que2.empty())
{
int u = que2.front();
que2.pop();
for(int k : a[u])
{
ans[k] += flag[u];
}
ind[fail[u]]--;
flag[fail[u]] += flag[u];
if(!ind[fail[u]])
{
que2.push(fail[u]);
}
}
}
int main()
{
scanf("%d", &n);
string str2 = "";
for(int i = 0; i < n; i++)
{
string str3;
cin >> str3;
insert(str3, i);
}
cin >> str2;
get_fail();
find(str2);
topo();
for(int i = 0; i < n; i++)
{
printf("%d\n", ans[i]);
}
}
例题
P3808 【模板】AC自动机(简单版) \color{3498DB}{\texttt{P3808 【模板】AC自动机(简单版)}} P3808 【模板】AC自动机(简单版)
P3796 【模板】AC自动机(加强版) \color{3498DB}{\texttt{P3796 【模板】AC自动机(加强版)}} P3796 【模板】AC自动机(加强版)
P5357 【模板】AC自动机(二次加强版) \color{9D3DCF}{\texttt{P5357 【模板】AC自动机(二次加强版)}} P5357 【模板】AC自动机(二次加强版)
P5231 [JSOI2012]玄武密码 \color{9D3DCF}{\texttt{P5231 [JSOI2012]玄武密码}} P5231 [JSOI2012]玄武密码