题目链接

AC自动机

解题思路

AC自动机模板题。
刚学AC自动机,写一篇博客增强理解。
AC自动机最关键的一点在于,\(fail\)失配指针的构造。
\(fail\)指针指向的地方,是匹配出现错误后进行重新匹配的位置,这说明,从根开始到\(fail\)指针指向的地方这一块字符串,正是我们刚刚失配之前配上的那一块字符串(子串),且为最长子串。这一点和KMP算法相同。

AC代码

#include<stdio.h>
#include<string.h>
int ac[100010][26],cnt=1;
int queue[100010],fail[100010],end[100010];
char a[160][75],m[1000010];
struct {
    int num,cnt;
}ans[500],temp;
void push(char a[],int l,int num){//建立trie树,很好理解不再赘述
    int i,now=0;
    for(i=0;i<l;i++){
        int v=a[i]-'a';
        if(!ac[now][v])ac[now][v]=cnt++;
        now=ac[now][v];
    }
    end[now]=num;
}
void build(){
    int head=0,tail=0,i;//C党手写queue
    for(i=0;i<26;i++)if(ac[0][i]){
        queue[tail++]=ac[0][i];//push
        fail[ac[0][i]]=0;
    }
    while(head<tail){
        int v=queue[head++];//pop
        for(i=0;i<26;i++){
            if(ac[v][i]){
                fail[ac[v][i]]=ac[fail[v]][i];
                queue[tail++]=ac[v][i];//push
            }
            else ac[v][i]=ac[fail[v]][i];
            //该节点的失配指针,指向该节点的父节点的失配指针所指向的节点的子节点
            //也即,假设ac[v][i]的父节点失配指针指向的节点为E,则
            //从根节点到E的这个串,为从根节点到v这个串的最长共同结尾子串
            //那么,ac[v][i]的失配指针应当指向E的第i个节点(保证这一位字符相同)
            //if和else的作用:一个是失配指针,一个是trie图。
            //如果这点没有后续了,只能建立trie图。
            //否则,应当建立失配指针。 
        }
    }
}
void query(char a[],int l){
    int i,j,now=0;
    for(i=0;i<l;i++){
        now=ac[now][a[i]-'a'];//沿着建立好的trie图走
        for(j=now;j;j=fail[j])ans[end[j]].cnt++;//找到单词末尾并存储个数
    }
}
//C党手写快排
int cmp(int x,int y){
    if(ans[x].cnt>ans[y].cnt)return 1;
    if(ans[x].cnt<ans[y].cnt)return 0;
    if(ans[x].num<ans[y].num)return 1;
    return 0;
}
void qs(int left,int right){
    int i=left,j=right;
    if(i>=j)return;
    while(i!=j){
        for(;i<j;j--)if(cmp(j,left))break;
        for(;i<j;i++)if(cmp(left,i))break;
        if(i!=j){
            temp=ans[i];ans[i]=ans[j];ans[j]=temp;
        }
    }
    j=left;
    temp=ans[i];ans[i]=ans[j];ans[j]=temp;
    qs(left,i-1);
    qs(i+1,right);
}
int main(){
    int i,n;
    while(scanf("%d",&n)){
        if(!n)break;
        cnt=1;
        for(i=1;i<=n;i++){
            scanf("%s",a[i]);
            push(a[i],strlen(a[i]),i);//建立trie树
            ans[i].num=i;
            ans[i].cnt=0;
        }
        build();//构造AC自动机的fail指针,以及完善trie树成为trie图
        scanf("%s",m);
        query(m,strlen(m));//询问文本串
        //以下为本题特色,不是AC自动机精髓,可跳过
        qs(1,n);
        printf("%d\n",ans[1].cnt);
        printf("%s\n",a[ans[1].num]);
        for(i=2;i<n;i++){
            if(ans[i].cnt-ans[i-1].cnt)break;
            printf("%s\n",a[ans[i].num]);
        }
        memset(end,0,sizeof(int)*cnt);
        memset(fail,0,sizeof(int)*cnt); 
        memset(ac,0,sizeof(ac));    
    }
    return 0;
}