前言:
AC自动机与KMP算法,都是用于优化字符串匹配的算法。
KMP算法应用于单模式串的匹配。
而AC自动机是应用于多模式串的匹配。
但并不意味着KMP算法可以被完全取代(尽管两者的算法思想本质上是一样的)。
KMP算法:
相对而言,KMP算法比较容易一些。
首先,考虑如何暴力匹配?很显然,可以以主串的任意一个位置开头,然后开始匹配,失配后,再找下一个位置开头。
这样的复杂度显然是O(n*m)的。非常低劣。
而KMP算法则是用于优化这个过程的。显然,每次重新匹配时,如果能利用之前匹配的信息,那么就会节约很多时间。这里就要引入fail数组。 f a i l i fail_i faili表示与前缀 i i i拥有相同后缀的,最靠后的位置。这样如果在i号位置失配,那么可以跳到其fail指针的位置,继续匹配。
这样做的复杂度很显然就是O(n+m)的。
板子:
void build(int x){
int i=0,j=-1;
fail[0]=-1;
while(i<len[x]){
if(j==-1||s1[x][i]==s1[x][j]){
i++;
j++;
fail[i]=j;
}
else
j=fail[j];
}
}
void solve(int x){
int i=0,j=0;
while(i<m){
if(j==-1||s[i]==s1[x][j]){
i++;
j++;
}
else
j=fail[j];
if(j==len[x])
ch[i-len[x]]|=(1<<(x-1));
}
}
KMP算法实例:
1、BZOJ4974字符串大师
2、BZOJ3670动物园
3、BZOJ4560字符串覆盖
4、BZOJ4820硬币游戏
5、BZOJ2384[Ceoi2011]Match
AC自动机
AC自动机则是将fail指针带到了trie树上。
原理还是一样的。 f a i l i fail_i faili与 i i i拥有相同后缀的深度最大的点。
每次失配后也是往着 f a i l i fail_i faili跑。
AC自动机算法实例:
1、BZOJ1444有趣的游戏
2、BZOJ1559密码
3、BZOJ4861魔法咒语
板子:
struct node{
int tag;
char las;
node *fail;
node *ch[30];
}Tree[MAXN];
node* rt=Tree,*ncnt=Tree;
void insert(node *now,char *c,int id){
if(*c==0){
now->tag=id;
return ;
}
int d=*c-'a';
if(now->ch[d]==NULL){
ncnt++;
now->ch[d]=ncnt;
now->ch[d]->las=*c;
}
insert(now->ch[d],++c,id);
}
queue<node *> q;
void build(){
q.push(rt);
while(!q.empty()){
node *now=q.front();
q.pop();
for(int i=0;i<26;i++)
if(now->ch[i]!=NULL){
node *x=now->fail;
q.push(now->ch[i]);
if(now==rt){
now->ch[i]->fail=now;
continue;
}
while(x!=rt&&x->ch[i]==NULL)
x=x->fail;
if(x->ch[i]==NULL)
now->ch[i]->fail=rt;
else
now->ch[i]->fail=x->ch[i];
}
}
}