什么是ac自动机?(最开始的幻想是能够自动ac程序题的程序>_<,美梦啊!)
ac自动机全称叫做Aho-Corasick自动机(不是accept)。我们都知道在模式匹配,如果只有一个模板我们使用的是KMP算法,但是如果存在很多的模板呢?我们应该如何做呢?难道将每次查询一个模板都要遍历一遍整个字符串?这样时间复杂度肯定会不够的,所有我们就要使用一种新的算法,它就是ac自动机。
ac自动机其实就是将trie(前缀树)与KMP算法结合起来,将所有模板建立一颗前缀树,然后在计算出一个Fail函数。
Fail函数的作用就是建立其下图中虚线的边。
我的模板:
struct Aho-Corasick{
int ch[maxnode][sigma_size];
int val[maxnode];
int f[maxnode];
int last[maxnode];
int sz;
void clears() {sz=1;memset(ch[0],0,sizeof(ch[0]));}
int idx(char c){return c-'a';}
int insert(char *s,int v){
int u=0,n=strlen(s);
for(int i=0;i<n;i++){
int c=idx(s[i]);
if (!ch[u][c]) {
memset(ch[sz],0,sizeof(ch[sz]));
val[sz]=0;
ch[u][c]=sz++;
}
u=ch[u][c];
}
val[u]=v;
return 0;
}
int getFail(){
queue <int> q;
f[0]=0;
//初始化队列
for (int c=0;c<sigma_size;c++){
int u=ch[0][c];
if (u){f[u]=0;q.push(u);last[u]=0;}
}
//BFS 顺序计算失配函数
while (!q.empty()){
int r=q.front();q.pop();
for (int c=0;c<sigma_size;c++){
int u=ch[r][c];
if (!u) continue;
q.push(u);
int v=f[r];
while (v&&!ch[v][c]) v=f[v];
f[u]=ch[v][c];
last[u]=val[f[u]]?f[u]:last[f[u]];
}
}
}
void find (char *T){
int n=strlen(T);
int j=0;
for (int i=0;i<n;i++){
int c=idx(T[i]);
while (j&&!ch[j][c]) j=f[j];
j=ch[j][c];
if (val[j]) print(j);
else if (last[j]) print(last[j]);
}
}
void print(int j){
if (j){
printf("%d: %d\n",j,val[j]);
print(last[j]);
}
}
}
如果读者对trie与KMP算法都很熟悉的话,应该可以看得懂上面的代码,但是与也会存在一些问题比如last[]数组是什么?
和trie一样,我们认为所有val[j]>0的节点都是单词节点,但是与trie不同的是,同一个节点可能对应多个字符串结尾如上图中的5号节点,它分别对应了{he,she}
这个last[j]代表的是沿着虚线走,遇到的下一个单词节点编号,我们用它来降低时间复杂度。它还有个高大上的名字叫做后缀链接。
uva 1449 是一道裸地ac自动机题,可以用它作为练习.
我的代码:
#include <cstdio>
#include <cstring>
#include <queue>
#include <map>
#include <string>
const int sigma_size =26;
const int maxnode = 11000;
using namespace std;
struct AC{
int ch[maxnode][sigma_size];
int val[maxnode];
int f[maxnode];
int last[maxnode];
int cnt[maxnode];
int sz;
void clears() {sz=1;memset(ch[0],0,sizeof(ch[0]));memset(cnt,0,sizeof(cnt));}
int idx(char c){return c-'a';}
int insert(char *s,int v){
int u=0,n=strlen(s);
for(int i=0;i<n;i++){
int c=idx(s[i]);
if (!ch[u][c]) {
memset(ch[sz],0,sizeof(ch[sz]));
val[sz]=0;
ch[u][c]=sz++;
}
u=ch[u][c];
}
val[u]=v;
return 0;
}
int getFail(){
queue <int> q;
f[0]=0;
for (int c=0;c<sigma_size;c++){
int u=ch[0][c];
if (u){f[u]=0;q.push(u);last[u]=0;}
}
while (!q.empty()){
int r=q.front();q.pop();
for (int c=0;c<sigma_size;c++){
int u=ch[r][c];
if (!u) continue;
q.push(u);
int v=f[r];
while (v&&!ch[v][c]) v=f[v];
f[u]=ch[v][c];
last[u]=val[f[u]]?f[u]:last[f[u]];
}
}
}
void find (char *T){
int n=strlen(T);
int j=0;
for (int i=0;i<n;i++){
int c=idx(T[i]);
while (j&&!ch[j][c]) j=f[j];
j=ch[j][c];
if (val[j]) print(j);
else if (last[j]) print(last[j]);
}
}
void print(int j){
if (j){
cnt[val[j]]++;
print(last[j]);
}
}
};
AC ac;
map <string,int> qqq;
char text[1000010],P[155][80];
int n;
int main (){
while (scanf("%d",&n)==1&&n){
ac.clears();
for (int i=1;i<=n;i++){
scanf("%s",P[i]);
ac.insert(P[i],i);
}
ac.getFail();
scanf("%s",text);
ac.find(text);
int best=-1;
qqq.clear();
for (int i=1;i<=n;i++){
if (ac.cnt[i]>best) best=ac.cnt[i];
//printf("%d %d\n",i,ac.cnt[i]);
}
for (int i=1;i<=n;i++){
if (ac.cnt[i]==best) qqq[string(P[i])]=1;
}
printf("%d\n",best);
for (int i=1;i<=n;i++)
if (qqq[string(P[i])]) printf("%s\n",P[i]);
}
return 0;
}