Loading

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;
}
posted @ 2018-12-06 21:11  Potassium  阅读(288)  评论(0编辑  收藏  举报