P3796 【模板】AC自动机(加强版) 题解(Aho-Corasick Automation)

题目链接

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;
}

原文地址:https://www.cnblogs.com/Potassium/p/10079569.html

时间: 2024-11-08 07:47:16

P3796 【模板】AC自动机(加强版) 题解(Aho-Corasick Automation)的相关文章

算法模板——AC自动机

实现功能——输入N,M,提供一个共计N个单词的词典,然后在最后输入的M个字符串中进行多串匹配(关于AC自动机算法,此处不再赘述,详见:Aho-Corasick 多模式匹配算法.AC自动机详解.考虑到有时候字典会相当稀疏,所以引入了chi和bro指针进行优化——其原理比较类似于邻接表,这个东西本身和next数组本质上是一致的,只是chi和bro用于遍历某一节点下的子节点,next用于查询某节点下是否有需要的子节点) 1 type 2 point=^node; 3 node=record 4 ex:

[模板]AC自动机(1)

题目描述 给定一个文本串和多个模式串,求有几个模式串出现在文本串中 #include <cstdio> #include <cstring> #include <algorithm> #define MAXN 1000005 char s[MAXN]; int N; struct queue{ int que[MAXN];int head,tail; queue():head(1),tail(0){} inline void pop(){head++;} inline

模板——AC自动机

#include<bits/stdc++.h> using namespace std; struct nob{ int fail,son[27],ed; }a[1000000]; int cnt=0; void build (string s){ int now=0; for (int i=0; i<s.length(); i++){ if (a[now].son[s[i]-'a']==0) a[now].son[s[i]-'a']=++cnt; now=a[now].son[s[i]

AC自动机例题

P3808 [模板]AC自动机(简单版) [题目描述] 给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过. #include<bits/stdc++.h> using namespace std; typedef long long LL; const int INF=1e9+7; inline LL read(){ register LL x=0,f=1;register char c=getchar(); while(c<48||c>57){if(c=='-')f=

【题解】P3796【模板】AC自动机(加强版)

[题解]P3796 [模板]AC自动机(加强版) 记录当前\(cnt\)是第几个"星".记录第几个串是对应着第几个星. 这里补充一点对于\(AC\)自动机的理解.可能一直有个问题我没有想明白,就是打标记的点只有一个,然而匹配时,假若一个分支包括了另一个不同的分支该怎么办.实际上,我们可以在匹配的时候使用\(fail\)数组进行类似链式前向星的遍历,从而遍历到那个打标记的地方.那么问题来了,怎么保证链式前向星会遍历到那个打了标记的节点呢?答案就在\(gen\_fail\)的玄机里.\(g

P3796 【模板】AC自动机(加强版)

题目描述 有个由小写字母组成的模式串以及一个文本串.每个模式串可能会在文本串中出现多次.你需要找出哪些模式串在文本串中出现的次数最多. 输入输出格式 输入格式: 输入含多组数据. 每组数据的第一行为一个正整数,表示共有个模式串,. 接下去行,每行一个长度小于等于的模式串.下一行是一个长度小于等于的文本串. 输入结束标志为. 输出格式: 对于每组数据,第一行输出模式串最多出现的次数,接下去若干行每行输出一个出现次数最多的模式串,按输入顺序排列. 输入输出样例 输入样例#1: 2 aba bab a

luogu P3796【模板】AC自动机(加强版)

嘟嘟嘟 这个和某谷的AC自动机模板简单版差不多. 但还是要注意几点的: 1.这个是统计出现次数,而不是是否出现,所以在查询的时候加上这个节点的val后,不能把val标记为-1.那么也就可以说查询的时间复杂度能比简单版的稍微第一慢一点. 2.考虑k个一样的模式串:刚开始我想的是每一个节点开一个vector,记录这里是第几个模式串.但其实没有这个必要,对于相同的模式串,我们只用记录任意一个就行,反而在出现次数上要都加上.因为如果主串中存在这些相同的模式串,那么出现次数应该是出现次数 * k.输出的时

LG5357 「模板」AC自动机(二次加强版) AC自动机+fail树

问题描述 LG5357 题解 不是fail树的AC自动机复杂度是假的. 把AC自动机搞出来,建立Trie树,树上爆搜一遍就好了. \(\mathrm{Code}\) #include<bits/stdc++.h> using namespace std; template <typename Tp> void read(Tp &x){ x=0;char ch=1;int fh; while(ch!='-'&&(ch>'9'||ch<'0')) c

【模板】AC自动机

来自洛谷的两道AC自动机模板题: [模板]AC自动机(简单版) 题目背景 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 管理员提示:本题数据内有重复的单词,且重复单词应该计算多次,请各位注意 题目描述 给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过. 输入输出格式 输入格式: 第一行一个n,表示模式串个数: 下面n行每行一个模式串: 下面一行一个文本串. 输出格式: 一个数表示答案 输入输出样例 输