什么是Ac自动机
学会Ac自动机前提是要知道tire树和kmp的实现和原理
Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。
要学会AC自动机,我们必须知道什么是Trie,也就是字典树。Trie树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。
一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。
Ac自动机的实现
- 构造trie树
tr[N][M]//以N为结点的M个儿子节点
cnt[N]//以N为结尾元素的字符串的个数
void insert()
{
int p=0;
for(int i=0;str[i];i++)
{
int t = str[i]-'a';
if(!tr[p][t]) tr[p][t] = ++idx;
p = tr[p][t];
}
cnt[p]++;
}
- 构造类似kmp中next的数组
void build()
{
queue<int> qu;
//将所有trie树中第一层的结点加入队列
for(int i=0;i<26;i++)
if(tr[0][i]) qu.push(tr[0][i]);
whille(qu.size())
{
int t = qu.front();
qu.pop();
//枚举当前队头的26个分支
for(int i=0;i<26;i++)
{
int p = tr[t][i];
//如果存在我们就让它的ne指针指向他父亲节点的 ne指针指向的那个节点(根)的具有相同字母的子节点
if(p)
{
tr[t][i] = tr[ne[t]][i];
qu.push(tr[t][i])
}
//就算不存在,不跳,他的树节点值也等于父节点的ne指向的节点中具有相同字母的子节点
else
{
tr[t][i] = tr[ne[t]][i];
}
}
}
}
- 实现匹配函数
int res = 0;
for(int i=0,j=0;str[i];++i)
{
int u = str[i]-'a';
j = tr[j][u];
int p=j;
while(p){
res+=cnt[p];
cnt[p] = 0;
p = ne[p];
}
cout<<res<<endl;
}
模板例题
代码如下
#include<bits/stdc++.h>
using namespace std;
const int N = 10010,S=55,M=1000010;
int n;
int tr[N*S][26],cnt[N*S],idx;
char str[M];
int q[N*S],ne[N*S];
void insert()
{
int p=0;
for(int i=0;str[i];i++)
{
int t = str[i]-'a';
if(!tr[p][t]) tr[p][t] = ++idx;
p = tr[p][t];
}
cnt[p] ++ ;
}
void build()
{
queue<int> qu;
for(int i=0;i<26;i++)
if(tr[0][i])
qu.push(tr[0][i]);
while(qu.size())
{
int t = qu.front();
qu.pop();
for(int i=0;i<26;i++)
{
int p=tr[t][i];
if(!p) tr[t][i] = tr[ne[t]][i];
else
{
ne[p] = tr[ne[t]][i];
qu.push(p);
}
}
}
}
int main()
{
int T;
cin>>T;
while(T--)
{
memset(tr,0,sizeof tr);
memset(cnt,0,sizeof cnt);
memset(ne,0,sizeof ne);
idx = 0;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>str;
insert();
}
build();
cin>>str;
int res = 0;
for(int i=0,j=0;str[i];i++)
{
int t = str[i]-'a';
j = tr[j][t];
int p=j;
while(p)
{
res+=cnt[p];
cnt[p]=0;
p=ne[p];
}
}
cout<<res<<endl;
}
return 0;
}