【HDU2222】【Keywords Search】AC自动机,有详细注释题解。

题意:给定N个单词,和一个字符串S,求这N个单词在字符串S中,有多少个出现过。

题解:AC自动机裸题一枚。

AC自动机是基于字典树的一种KMP思想高级算法,用于多字串匹配。就是把字典树建好,然后模仿KMP的前缀数组“pre[]”,在字典树内处理了一个fail(失败指针),失配时顺着往前找,并寄托于此以得到答案。

直接附代码,里面有详解。(数组模拟版!!!指针神马的都去回收站吧!)

结构体+注释版本:

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 250010/*HDU上实际数据范围略小,正常应该开50W*/
#define M 26/*26个字母*/
using namespace std;

struct Trie/*字典树节点结构体*/
{
	int next[M],fail;/*fail,失败指针*/
	short cnt;/*当前节点是多少个单词的结尾*/
}trie[N];

int root,cnt,ans,n;/*根默认为0*/
char t[60],s[1001000];/*t是用于建树的临时输入数组*/

int i,u,v,temp,alp,star;/*此处全为临时变量*/

void init()/*每组测试数据要清一下!*/
{
	root=cnt=ans=0;
	memset(trie,0,sizeof(trie));
}
void insert()/*建字典树*/
{
	scanf("%s",t+1);/*字典树建树过程不赘述了*/
	for(temp=root,i=1;t[i];i++)
	{
		alp=t[i]-'a';/*当前枚举到字母转化成数字*/
		if(!trie[temp].next[alp])trie[temp].next[alp]=++cnt;
		temp=trie[temp].next[alp];
	}
	trie[temp].cnt++;
}
queue<int>q;
void acauto()/*处理出失败指针*/
{
	while(!q.empty())q.pop();
	/*(在函数内开队列,貌似就不需要清,就能省下时间,
			但是退出时系统是一定会清掉这些数据的,所以反而要慢。)*/
	q.push(root);
	while(!q.empty())
	{
		u=q.front();q.pop();
		for(i=0;i<M;i++)if(v=trie[u].next[i])
		{/*在此赘述一下AC自动机失败指针代表字串含义:
			就是当前节点所代表字符串的一个最长后缀,
				此后缀满足有一个不相同的root连接前缀与之相同
			没有深刻理解的童鞋可以手调几个数据,还是那句话:“手调有助于深刻理解”
		*/
			if(u==root)trie[v].fail=root;/*这里只是一个防错处理,
											因为root连着的节点不可能有上述后缀*/
			else
			{
				temp=trie[u].fail;/*从v的角度看,是找到了一个父亲的失败指针*/
				while(temp&&!trie[temp].next[i])temp=trie[temp].fail;
				/*从这个失败指针为起点,寻找尽可能长的后缀,即找相同字母的节点*/
				trie[v].fail=trie[temp].next[i];
				/*然后失败指针被赋值*/
			}
			q.push(v);
		}
	}
}

void handle()
{/*此函数是依托AC自动机来解决多字串匹配问题。*/
	scanf("%s",s+1);
	for(temp=root,i=1;s[i];i++)
	{
		alp=s[i]-'a';
		while(temp&&!trie[temp].next[alp])temp=trie[temp].fail;
		/*if(trie[temp].next[alp])*/star=temp=trie[temp].next[alp];
			/*本代码next不存在则=0,所以可以无视注释掉的if代码*/
		while(trie[star].cnt)
		{
			ans+=trie[star].cnt;
			trie[star].cnt=0;
			star=trie[star].fail;
		}
		/*因为上面几行代码略有缺陷,锁业下面有点补救措施*/
		/*可以在样例数据的基础上再加一个字串“h”即可卡掉不加下面两行的代码*/
		/*原因:若一个点本身是单词结尾,失败指针指向不是,
			而失败指针的失败指针又是单词结尾节点,哼哼~~~*/
		ans+=trie[trie[star].fail].cnt;
		trie[trie[star].fail].cnt=0;
	}
}
/*
		在此提供另一种得到答案的思想,
			即看该节点扫没扫过。
		for(temp=root,i=1;s[i];i++)
		{
			alp=s[i]-'a';
			while(temp&&!trie[temp].next[alp])temp=trie[temp].fail;
			star=temp=trie[temp].next[alp];
			while(star&&!visit[temp])
			{
				ans+=trie[star].cnt;
				star=trie[star].fail;
				visit[temp]=1;
			}
		}
*/

int main()
{
//	freopen("test.in","r",stdin);
	int i,g,_g;
	scanf("%d",&_g);
	for(g=1;g<=_g;g++)
	{
		init();
		scanf("%d",&n);
		for(i=1;i<=n;i++)insert();/*建树*/
		acauto();/*处理失败指针*/
		handle();/*进行匹配得到答案*/
		printf("%d\n",ans);
	}
	return 0;
}

结构体无注释版:

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 250010
#define M 26
using namespace std;

struct Trie
{
	int next[26],fail;
	short cnt;
}trie[N];

int root,cnt,ans,n;
char t[60],s[1001000];

int i,u,v,temp,alp,star;

void init()
{
	root=cnt=ans=0;
	memset(trie,0,sizeof(trie));
}
void insert()
{
	scanf("%s",t+1);
	for(temp=root,i=1;t[i];i++)
	{
		alp=t[i]-'a';
		if(!trie[temp].next[alp])trie[temp].next[alp]=++cnt;
		temp=trie[temp].next[alp];
	}
	trie[temp].cnt++;
}
queue<int>q;
void acauto()
{
	while(!q.empty())q.pop();
	q.push(root);
	while(!q.empty())
	{
		u=q.front();q.pop();
		for(i=0;i<M;i++)if(v=trie[u].next[i])
		{
			if(u==root)trie[v].fail=root;
			else
			{
				temp=trie[u].fail;
				while(temp&&!trie[temp].next[i])temp=trie[temp].fail;
				trie[v].fail=trie[temp].next[i];
			}
			q.push(v);
		}
	}
}

void handle()
{
	scanf("%s",s+1);
	for(temp=root,i=1;s[i];i++)
	{
		alp=s[i]-'a';
		while(temp&&!trie[temp].next[alp])temp=trie[temp].fail;
		star=temp=trie[temp].next[alp];
		while(trie[star].cnt)
		{
			ans+=trie[star].cnt;
			trie[star].cnt=0;
			star=trie[star].fail;
		}
		ans+=trie[trie[star].fail].cnt;
		trie[trie[star].fail].cnt=0;
	}
	printf("%d\n",ans);
}

void build()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)insert();
}

int main()
{
	int i,g,_g;
	scanf("%d",&_g);
	for(g=1;g<=_g;g++)
	{
		init();
		build();
		acauto();
		handle();
	}
	return 0;
}

数组模拟结构体无注释版(代码短,就是短):

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 250010
#define M 26
using namespace std;

int next[N][26],fail[N];
short cnt[N];

int root,num,ans,n;
char t[60],s[1001000];

int i,u,v,temp,alp,star;

void init()
{
	root=num=ans=0;
	memset(fail,0,sizeof(fail));
	memset(cnt,0,sizeof(cnt));
}
void insert()
{
	scanf("%s",t+1);
	for(temp=root,i=1;t[i];i++)
	{
		alp=t[i]-'a';
		if(!next[temp][alp])next[temp][alp]=++num;
		temp=next[temp][alp];
	}
	cnt[temp]++;
}
queue<int>q;
void acauto()
{
	while(!q.empty())q.pop();
	q.push(root);
	while(!q.empty())
	{
		u=q.front();q.pop();
		for(i=0;i<M;i++)if(v=next[u][i])
		{
			if(u==root)fail[v]=root;
			else
			{
				temp=fail[u];
				while(temp&&!next[temp][i])temp=fail[temp];
				fail[v]=next[temp][i];
			}
			q.push(v);
		}
	}
}

void handle()
{
	scanf("%s",s+1);
	for(temp=root,i=1;s[i];i++)
	{
		alp=s[i]-'a';
		while(temp&&!next[temp][alp])temp=fail[temp];
		star=temp=next[temp][alp];
		while(cnt[star])
		{
			ans+=cnt[star];
			cnt[star]=0;
			star=fail[star];
		}
		ans+=cnt[fail[star]];
		cnt[fail[star]]=0;
	}
	printf("%d\n",ans);
}

void build()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)insert();
}

int main()
{
	int i,g,_g;
	scanf("%d",&_g);
	for(g=1;g<=_g;g++)
	{
		init();
		build();
		acauto();
		handle();
	}
	return 0;
}
时间: 2024-07-31 10:09:52

【HDU2222】【Keywords Search】AC自动机,有详细注释题解。的相关文章

hdu2222 Keywords Search ac自动机

地址:http://acm.split.hdu.edu.cn/showproblem.php?pid=2222 题目: Keywords Search Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)Total Submission(s): 56558    Accepted Submission(s): 18493 Problem Description In the mo

hdu2222 Keywords Search &amp; AC自动机学习小结

传送门:http://http://acm.hdu.edu.cn/showproblem.php?pid=2222 思路:AC自动机入门题,直接上AC自动机即可. 对于构建AC自动机,我们要做的只有三件事: 1)构建字典树 2)构建失败指针 3)构建trie图(这道题好像不做这一步也能A...但是这一步不做是会被卡成O(n^2)的...) 1)第一步还是比较好理解的 根是虚根,边代表字母,那么根到终止节点的路径就是一个字符串,这样对于前缀相同的字符串我们就可以省下存公共前缀的空间. 加入一个模式

HDU 2222 Keywords Search AC自动机入门题

单词统计的题目,给出一些单词,统计有多少单词在一个文本中出现,最经典的入门题了. AC自动机的基础: 1 Trie, 以这个数据结构为基础的,不过增加一个fail指针和构造fail的函数 2 KMP,不是直接运用KMP,而是需要KMP的思想,KMP思想都没有的话,理解这个算法会更加吃力的. 注意本题的单词会有重复出现的,一个单词只能统计一次. 搜索了一下网上的题解,发现好多代码都是一大抄的啊,⊙﹏⊙b汗. 本博客的乃是原创代码,代码风格也是差不多固定的,转载请注明出处:http://blog.c

[hdu2222] [AC自动机模板] Keywords Search [AC自动机]

AC自动机模板,注意!ch,Fail,lab数组的大小不是n而是节点个数,需要认真计算! 1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cstring> 5 #include <cmath> 6 #include <ctime> 7 #include <cstdlib> 8 #include <queue>

HDU 2222 Keywords Search AC自动机

Keywords Search Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)Total Submission(s): 67122    Accepted Submission(s): 22584 Problem Description In the modern time, Search engine came into the life of everybody lik

hdu 2222 Keywords Search(ac自动机入门题)

1 /************************************************************ 2 题目: Keywords Search(hdu 2222) 3 链接: http://acm.hdu.edu.cn/showproblem.php?pid=2222 4 算法: ac自动机 5 算法思想: 多个字符串匹配,也就是相当于多个kmp 6 ***********************************************************

HDU 2222 Keywords Search (AC自动机入门 模板)

AC自动机入门 Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一.学习AC自动机之前得先有Trie树和KMP模式匹配算法的基础. AC自动机算法分为3步:1.构造一棵tire树  2.构造失败指针  3.进行模式匹配 AC自动机的优化:Trie图 Keywords Search Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Other

HDU 2222 Keywords Search (AC自动机模板题)

Keywords Search Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)Total Submission(s): 67950    Accepted Submission(s): 22882 Problem Description In the modern time, Search engine came into the life of everybody lik

Match:Keywords Search(AC自动机模板)(HDU 2222)

多模匹配 题目大意:给定很多个字串A,B,C,D,E....,然后再给你目标串str字串,看目标串中出现多少个给定的字串. 经典AC自动机模板题,不多说. 1 #include <iostream> 2 #include <algorithm> 3 #include <functional> 4 #include <string.h> 5 #define MAX 26 6 7 using namespace std; 8 9 struct node 10 {

hdoj 2222 Keywords Search(AC自动机)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2222 思路分析:该问题为多模式匹配问题,使用AC自动机解决:需要注意的问题是如何统计该待查询的字符串包含的关键字: 假设待查找的字符串为str[0..n],则str[i…j]可能为某一个关键字:假设当前正在匹配字符str[k],则以str[i..k]为关键字的所有可能 可能的关键字的最后一个字符为str[k],使用fail指针进行跳转并判断以str[k]结尾的该结点是否为关键字最后一个结点,重复进行