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

24 篇文章 0 订阅
23 篇文章 0 订阅

某模板题:链接:https://www.luogu.org/problemnew/show/P3796

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

描述

有N个由小写字母组成的模式串以及一个文本串T。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串T中出现的次数最多。

输入

输入含多组数据。

每组数据的第一行为一个正整数N,表示共有N个模式串,1≤N≤150。

接下去NN行,每行一个长度小于等于70的模式串。下一行是一个长度小于等于10^6 的文本串T。

输入结束标志为N=0

输出

对于每组数据,第一行输出模式串最多出现的次数,接下去若干行每行输出一个出现次数最多的模式串,按输入顺序排列。

样例输入
2
aba
bab
ababababac
6
beta
alpha
haha
delta
dede
tata
dedeltalphahahahototatalpha
0
样例输出
4
aba
2
alpha
haha

概论:

AC自动机是KMP算法(思想)与trie树(形式)的结合。KMP运用于单模匹配,失败后,将已完成匹配节点的后缀与模板串前缀匹配,然后将模板串前缀移至匹配失败的地方继续下一轮匹配。
而AC自动机则是多模匹配,给出多个模板串,可看作是一个字符串的集合,用trie树来作为字符串的集合,再在trie树上构建KMP失配函数,方法则是

  • 构建trie树(将所有模板链构建成一个字典树)
  • 构建fail指针(每次匹配失败后回到指针所指向的点,类似于KMP的next指针作用)
  • 查询(利用字典树方法查询)
    标准代码:
    (某些大佬提供)
#include<cstdio>
#include<cstring>
#include<queue>
#include<iostream>
#define DEBUG printf("ASDASD");//dhy大佬教我的骚技能1
#define clear(x) memset(x,0,sizeof(x))//dhy大佬教我的骚技能2 
using namespace std;
const int N=1e6;
int tree[N][27],ans[N]/*ans[i]表示第i号点是否为一个单词结尾,若是,储存值为该单词编号,否则为0*/,fail[N];
int cnt=0/*以0号节点为初始节点(用什么表示虚根?)*/,root;
void insert(char s[],int v)
{
    int now=root;//now表示now号点 
    int len=strlen(s);
    for(int i=0;i<len;++i)
    {
        int id=s[i]-'a';
        if(!tree[now][id])
            tree[now][id]=++cnt;
        now=tree[now][id];
    }
    ans[now]=v;
    return;
}
void getfail()
{
    queue <int> q;
    for(int i=0;i<26;++i)//单独处理(虚)根节点 
    {
        if(tree[0][i])
        {
            q.push(tree[0][i]);
        }
    }
    while(!q.empty())//从(虚)根的子节点开始处理,默认虚根子节点fail都指向虚根 
    {
        int now=q.front();q.pop();
        for(int i=0;i<26;i++)
        {
            int v=tree[now][i];
            if(v)fail[v]=tree[fail[now]][i],q.push(v);
            else tree[now][i]=tree[fail[now]][i];
        }
    }
}
int tmp[N];
void query(char s[])
{
    int now=0,sum=0;
    int len=strlen(s);
    for(int i=0;i<len;++i)
    {
        now=tree[now][s[i]-'a'];
        for(int j = now;j;j=fail[j])tmp[ans[j]]++;///
    }
}
char T[200][200];//模板串 
char S[N];
int main()
{
    int n;
    while(1){
        scanf("%d",&n);
        if(n==0)return 0;
        clear(T),clear(S),clear(tmp),clear(tree),clear(ans),clear(fail);
        cnt=0;
        for(int i=1;i<=n;++i)
        {
            scanf("%s",T[i]);
            insert(T[i],i);
        }
        getfail();
        scanf("%s",S);
        query(S);
        int mx=0;
        for(int i=1;i<=n;i++)
            mx=max(mx,tmp[i]);
        printf("%d\n",mx);
        for(int i=1;i<=n;i++)
            if(tmp[i]==mx)
                printf("%s\n",T[i]);
        }
        return 0;
 } 

蒟蒻代码:

#include<cstdio>//问题:有模板串:ab与pabc 
#include<cstring>
using namespace std;
const int N=1e6+5;
int n,ch[10505/*大小开错*/][30],nxt[N],cnt=1,que[10505],maxn,f[155],bo[10505],t=0;
struct fjy{
    char st2[70];
    int num;
}z[155];
char st1[N],st3[30],ans[155][75];
void insert(fjy x){//构建trie树 
    char s[30];
    strcpy(s,x.st2);
    int u=1,len=strlen(s);
    for(int i=0;i<len;i++){	
        int c=s[i]-'a';
    /*	printf("%d ",c);*/
        if(!ch[u][c])
        ch[u][c]=++cnt;//printf("%d ",cnt);
        u=ch[u][c];
    }
    bo[u]=x.num;//printf("%d %d",bo[u],cnt);/
    return;
}
void bfs(){//构建fail指针 
    for(int i=0;i<26;i++)
        ch[0][i]=1;
    que[1]=1,nxt[1]=0;
    for(int q1=1,q2=1;q1<=q2;q1++){
        int u=que[q1];
        for(int i=0;i<26;i++){
            if(!ch[u][i]) 
            {
            	ch[u][i]=ch[nxt[u]][i];//时间优化
            	/*if(ch[nxt[u]][i])
            	bo[nxt[u]]=bo[u];*/
			}
            else{
                q2++;
                que[q2]=ch[u][i];
                int v=nxt[u];
                nxt[ch[u][i]]=ch[v][i];//printf("%d\n",q1);
            }
        }
    }
}
void find(char *s)
{
    int u=1,c,k,len=strlen(s),ans1=0;
    for(int i=0;i<len;i++){
        c=s[i]-'a';
        u=ch[u][c];//printf("%d\n",u);u的遍历无问题 
        //ans1+=bo[u];
        /*if(bo[u]){///
            f[bo[u]]++;u=nxt[u];
            //printf("%d",f[1]); 
        }*/
        for(int j=u;j;j=nxt[j]) f[bo[j]]++;//
    }
    return;
}
int main()
{	
    scanf("%d",&n);
    while(n)
    {
        maxn=0;t=0;cnt=1;
        memset(nxt,0,sizeof(nxt));
        memset(f,0,sizeof(f));
        memset(que,0,sizeof(que));
        memset(bo,0,sizeof(bo));
        memset(ch,0,sizeof(ch));
        memset(st1,0,sizeof(st1));
        memset(st3,0,sizeof(st3));
        memset(ans,0,sizeof(ans));
        for(int i=0;i<26;i++)
            ch[0][i]=1,ch[1][i]=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%s",st3);
            z[i].num=i,strcpy(z[i].st2,st3/*,sizeof(st3)*/);
            insert(z[i]);
        }
        
        bfs();
        scanf("%s",st1);
        find(st1);
        for(int i=1;i<=n;i++)
        {
            if(maxn<f[i]){
                maxn=f[i];t=0;
                memset(ans,0,sizeof(ans));
                strcpy(ans[++t],z[i].st2);
            }
            else
            if(maxn==f[i])
                strcpy(ans[++t],z[i].st2);
        }
        printf("%d\n",maxn);
        for(int i=1;i<=t;i++)printf("%s\n",ans[i]);
        scanf("%d",&n);
    }
    return 0;
}	

来自蒟蒻的挣扎:

#include<cstdio>//调试剂 
#include<cstring>
using namespace std;
const int N=1e6+5;
int n,ch[10505][30],nxt[N],cnt=0,que[10505],maxn,f[155],bo[10505],t=0;
struct fjy{//模板串的相关信息 
    char st2[70];
    int num;
}z[155];
char st1[N],st3[30],ans[155][75];
void insert(fjy x){//构建trie树 
    char s[30];
    strcpy(s,x.st2);
    int u=0,len=strlen(s);
    for(int i=0;i<len;i++){
        int c=s[i]-'a';
    /*	printf("%d ",c);*/
        if(!ch[u][c])
        ch[u][c]=++cnt;//printf("%d ",cnt);
        u=ch[u][c];
    }
    bo[u]=x.num;//printf("%d %d",bo[u],cnt);/
    return;
}
/*void bfs(){//构建fail指针 
    for(int i=0;i<26;i++)
        ch[0][i]=1;
    que[1]=1,nxt[1]=0;
    for(int q1=1,q2=1;q1<=q2;q1++){
        int u=que[q1];
        for(int i=0;i<26;i++){
            if(!ch[u][i]) 
            
            	ch[u][i]=ch[nxt[u]][i];//时间优化
			
            else{
                q2++;
                que[q2]=ch[u][i];
                int v=nxt[u];
                nxt[ch[u][i]]=ch[v][i];//printf("%d\n",q1);
            }
        }
    }
}*/
void bfs(){
	int q1=1,q2=0;
	for(int i=0;i<26;i++)
		if(ch[0][i])
			que[++q2]=ch[0][i];
	for(q1,q2;q1<=q2;q1++){
		int u=q1;
		for(int i=0;i<26;i++){
			int v=ch[u][i];
			if(v){//存在目标子节点 
				nxt[v]=ch[nxt[u]][i];
				que[++q2]=v;
			}
			else{//不存在目标子节点
				ch[u][i]=ch[nxt[u]][i];
			}
		}
	}
}
void find(char *s)
{
    int u=0,c,k,len=strlen(s),ans1=0;
    for(int i=0;i<len;i++){
        c=s[i]-'a';
        u=ch[u][c];//printf("%d\n",u);u的遍历无问题 
        //ans1+=bo[u];
        /*if(bo[u]){///
            f[bo[u]]++;u=nxt[u];
            //printf("%d",f[1]); 
        }*/
        for(int j=u;j;j=nxt[j]) f[bo[j]]++;//
    }
    return;
}
int main()
{	
    scanf("%d",&n);
    while(n)
    {
        maxn=0;t=0;cnt=1;
        memset(nxt,0,sizeof(nxt));
        memset(f,0,sizeof(f));
        memset(que,0,sizeof(que));
        memset(bo,0,sizeof(bo));
        memset(ch,0,sizeof(ch));
        memset(st1,0,sizeof(st1));
        memset(st3,0,sizeof(st3));
        memset(ans,0,sizeof(ans));
        for(int i=0;i<26;i++)
            ch[0][i]=1,ch[1][i]=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%s",st3);
            z[i].num=i,strcpy(z[i].st2,st3/*,sizeof(st3)*/);
            insert(z[i]);
        }
        
        bfs();
        scanf("%s",st1);
        find(st1);
        for(int i=1;i<=n;i++)
        {
            if(maxn<f[i]){
                maxn=f[i];t=0;
                memset(ans,0,sizeof(ans));
                strcpy(ans[++t],z[i].st2);
            }
            else
            if(maxn==f[i])
                strcpy(ans[++t],z[i].st2);
        }
        printf("%d\n",maxn);
        for(int i=1;i<=t;i++)printf("%s\n",ans[i]);
        scanf("%d",&n);
    }
    return 0;
}	

蒟蒻的逆袭:

#include<cstdio>//问题:有模板串:ab与pabc 
#include<cstring>
using namespace std;
const int N=1e6+5;
int n,ch[10505/*大小开错*/][30],nxt[N],cnt=1,que[10505],maxn,f[155],bo[10505],t=0;
struct fjy{
    char st2[70];
    int num;
}z[155];
char st1[N],st3[75],ans[155][75];
void insert(fjy x){//构建trie树 
    char s[75];
    strcpy(s,x.st2);
    int u=1,len=strlen(s);
    for(int i=0;i<len;i++){	
        int c=s[i]-'a';
    /*	printf("%d ",c);*/
        if(!ch[u][c])
        ch[u][c]=++cnt;//printf("%d ",cnt);
        u=ch[u][c];
    }
    bo[u]=x.num;//printf("%d %d",bo[u],cnt);/
    return;
}
void bfs(){//构建fail指针 
    for(int i=0;i<26;i++)
        ch[0][i]=1;
    que[1]=1,nxt[1]=0;
    for(int q1=1,q2=1;q1<=q2;q1++){
        int u=que[q1];
        for(int i=0;i<26;i++){
            if(!ch[u][i]) 
            {
            	ch[u][i]=ch[nxt[u]][i];//时间优化
            	/*if(ch[nxt[u]][i])
            	bo[nxt[u]]=bo[u];*/
			}
            else{
                q2++;
                que[q2]=ch[u][i];
                int v=nxt[u];
                nxt[ch[u][i]]=ch[v][i];//printf("%d\n",q1);
            }
        }
    }
}
void find(char *s)
{
    int u=1,c,k,len=strlen(s),ans1=0;
    for(int i=0;i<len;i++){
        c=s[i]-'a';
        u=ch[u][c];//printf("%d\n",u);u的遍历无问题 
        //ans1+=bo[u];
        /*if(bo[u]){///
            f[bo[u]]++;u=nxt[u];
            //printf("%d",f[1]); 
        }*/
        for(int j=u;j;j=nxt[j]) f[bo[j]]++;//
    }
    return;
}
int main()
{	
    scanf("%d",&n);
    while(n)
    {
        maxn=0;t=0;cnt=1;
        memset(nxt,0,sizeof(nxt));
        memset(f,0,sizeof(f));
        memset(que,0,sizeof(que));
        memset(bo,0,sizeof(bo));
        memset(ch,0,sizeof(ch));
        memset(st1,0,sizeof(st1));
        memset(st3,0,sizeof(st3));
        memset(ans,0,sizeof(ans));
        for(int i=0;i<26;i++)
            ch[0][i]=1,ch[1][i]=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%s",st3);
            z[i].num=i,strcpy(z[i].st2,st3/*,sizeof(st3)*/);
            insert(z[i]);
        }
        
        bfs();
        scanf("%s",st1);
        find(st1);
        for(int i=1;i<=n;i++)
        {
            if(maxn<f[i]){
                maxn=f[i];t=0;
                memset(ans,0,sizeof(ans));
                strcpy(ans[++t],z[i].st2);
            }
            else
            if(maxn==f[i])
                strcpy(ans[++t],z[i].st2);
        }
        printf("%d\n",maxn);
        for(int i=1;i<=t;i++)printf("%s\n",ans[i]);
        scanf("%d",&n);
    }
    return 0;
}	
错误总结:
  • 虚根的建立:第一套蒟蒻程序中,以0节点为虚根,1节点为字典树的起始节点(然后就错了),第二套蒟蒻程序中,以0节点为虚根兼字典树的起始节点,对于起始节点的所有儿子单独处理,让所有儿子的fail指针都指向起始节点(然后就对了,至于为什么我也不知道)
  • 数据的范围:第三套蒟蒻程序中,st2[]的范围改为了75,字典树建立当中的s[]的范围也改为了75
  • 构建fail指针中,q1所指向的是队列中的点而不是点在字典树中的序号
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值