Ac自动机就是在一个字符串中,查找多个给出多个模式串,查找匹配。
多次kmp字符串匹配?在给出模式串数目特别大的情况下,即便是kmp也会超时,因此,我们这里有了ac自动机,即在tire上kmp,只需遍历一遍原字符串就能找出所有匹配。
tire树又称字典树,利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较 不会的看这里
我们需要的是在trie上kmp。
首先是建立一个trie树,这里就不过多赘述
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct tree{
int vis[26];
int end;
int fail;
}ac[10000005];
int cnt;
char s[10000005];
int queue[10000000],start,end;
void build(int n){
int i,j,k,l,now;
for(i=0;i<n;i++){
scanf("%s",s);
l=strlen(s),now=0;
for(j=0;j<l;j++){
if(ac[now].vis[s[j]-'a']==0)
ac[now].vis[s[j]-'a']=++cnt;
now=ac[now].vis[s[j]-'a'];
}
ac[now].end+=1;
}
}
然后就是寻匹配失败的回溯位置,即fail(看毛片) 。
先来定义一下: 匹配失败,即该节点不存在要查找的子节点。(这是容易混淆的一点)
fail指该结点匹配失败后,回溯到的结点 暂时不懂没关系
- 首先是根结点,匹配失败,则fail仍指向根结点
- 再对根结点的子节点进行讨论,若子节点匹配失败,则需要回到根结点重新匹配,fail指向根结点。
- 再对已经找到fail的结点,遍历他们的子节点。若某个子结点 i 匹配失败,将vis[i] 置为当前结点的fail的vis[i],即回溯过程。 若某个结点 i 的子节点存在,则将该子节点的fail置为原结点的fail的vis[i]。
第三步可能很难理解,这里给出一个结论,结合着这些结论,应该就不难理解。
每一个结点的fail所代表的字符串,是这个结点所代表的字符串的最大后缀匹配(kmp思想)。
数学归纳法证明:
首先,所有的根结点满足
假设第k层满足
对k+1层的某个结点i,它的fail是在第k层父节点的fail的vis[i],父节点的fail是它的最大后缀匹配
- 若fail的vis[i] 存在,那么k+1层的fail即第k+1最大后缀匹配
- 若fail的vis[i]不存在,fail的vis[i]存储的是fail的fail的vis[i],即fail的最大后缀的匹配的vis[i],若vis[i]存在,则是当前节点的最大后缀匹配,若不存在,则可一直向下,直到0,在这个过程中,可以保证不存在更长的最大后缀匹配。
这里是代码
void get_fail(){
int n,i,j,k,u;
for(i=0;i<26;++i){//第二层
if(ac[0].vis[i]!=0){
ac[ac[0].vis[i]].fail=0;//不匹配则指向0
queue[end++]=ac[0].vis[i];//加入队列
}
}
while(end!=start){
u=queue[start++];
for(i=0;i<26;i++){
if(ac[u].vis[i]==0)//若第i个不存在,则相当于匹配失败,访问u的fail的i
ac[u].vis[i]=ac[ac[u].fail].vis[i];
else ac[ac[u].vis[i]].fail=ac[ac[u].fail].vis[i],queue[end++]=ac[u].vis[i];// 存在该节点
}
}
}
如果上面理解了,下面的匹配过程也挺好理解
我们要查找模式串在给定字符串中出现种类个数
遍历字符串中的每一个字符,沿着建好的树走,对于到达的每一个结点,考虑到该结点字符串可能是别的字符串的后缀,因此,一直遍历fail结点。直到为根结点,将所有可能的结点都遍历完即可。
这里就是全部代码了
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct tree{
int vis[26];
int end;
int fail;
}ac[10000005];
int cnt;
char s[10000005];
int queue[10000000],start,end;
void build(int n){
int i,j,k,l,now;
for(i=0;i<n;i++){
scanf("%s",s);
l=strlen(s),now=0;
for(j=0;j<l;j++){
if(ac[now].vis[s[j]-'a']==0)
ac[now].vis[s[j]-'a']=++cnt;
now=ac[now].vis[s[j]-'a'];
}
ac[now].end+=1;
}
}
void get_fail(){
int n,i,j,k,u;
for(i=0;i<26;++i){//第二层
if(ac[0].vis[i]!=0){
ac[ac[0].vis[i]].fail=0;//不匹配则指向0
queue[end++]=ac[0].vis[i];//加入队列
}
}
while(end!=start){
u=queue[start++];
for(i=0;i<26;i++){
if(ac[u].vis[i]==0)//若第i个不存在,则相当于匹配失败,访问u的fail的i
ac[u].vis[i]=ac[ac[u].fail].vis[i];
else ac[ac[u].vis[i]].fail=ac[ac[u].fail].vis[i],queue[end++]=ac[u].vis[i];// 存在该节点
}
}
}
int ac_match(){
int l=strlen(s),i,j,now=0,count=0;
for(i=0;i<l;i++){
now=ac[now].vis[s[i]-'a'];
for(int t=now;t&&ac[t].end!=-1;t=ac[t].fail)count+=ac[t].end,ac[t].end=-1;
}
return count;
}
int main(){
int n;
scanf("%d",&n);
build(n);
get_fail();
scanf("%s",s);
printf("%d",ac_match());
}