自动机(hdu2222模板)

给定n个长度不超过50的由小写英文字母组成的单词准备查询,以及一篇长为m的文章,问:文中出现了多少个待查询的单词。多组数据。

输入格式
第一行一个整数t,表示数据组数;
对于每组数据,第一行一个整数n,接下去n行表示n个单词,最后一行输入一个字符串,表示文章。

输出格式
对于每组数据,输出一个数,表示文中出现了多少个待查询的单词。

样例
样例输入
1
5
she
he
say
shr
her
yasherhs
样例输出
3

以下代码和思路是菜鸡刚学ac自动机时学的...以后有所进步的话随时会更改0.0

#include <string.h>
#include <stdio.h>
#include <algorithm>
#include <string>
#include <queue>
#include <bits/stdc++.h>
using namespace std;
const int maxn=2*1e6+10;
int tree[maxn][26],word[maxn],fail[maxn];
int tot;
void insert(string s)
{
	int u=0,i;
	for (i=0; i<s.size(); i++)
	{
		int c=s[i]-'a';
		if (!tree[u][c])
		{
			tree[u][c]=++tot;
			memset(tree[tot],0,sizeof(tree[tot]));
			//清空tot行的元素
		}
		u=tree[u][c];
	}
	word[u]++;
}
void bfs()
{
	queue<int>q;
	int i,j;
	for (i=0; i<26; i++)//26个单词依次遍历
	{
		if (tree[0][i])
		{
			fail[tree[0][i]]=0;
			q.push(tree[0][i]);
		}
	}
//fail[now]->当前节点now的失败指针指向的地方
//tire[now][i]->下一个字母为i+'a'的节点的下标为tire[now][i]	 
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for (i=0; i<26; i++)
		{
			if (tree[u][i])//tree[u][i]是当前节点,u是他的父节点
			{
//如果有这个子节点为字母i+'a',则
//让这个节点的失败指针指向(((他父亲节点)
//的失败指针所指向的那个节点)的下一个节点)
				fail[tree[u][i]]=tree[fail[u]][i];
				q.push(tree[u][i]);
			}
			else
//否则就让当前节点的这个子节点
//指向当前节点fail指针的这个子节点
				tree[u][i]=tree[fail[u]][i];
		}
	}
}
int query(string s)
{
	int i,j,u=0,ans=0;
	for (i=0; i<s.size(); i++)
	{
		int c=s[i]-'a';
		u=tree[u][c];
		for (j=u; j&&word[j]!=-1; j=fail[j])
		{
//一直向下寻找,直到匹配失败(失败指针指向根或者当前节点已找过).
			ans+=word[j];
			word[j]=-1;
		}
	}
	return ans;
}
int main()
{
	int i,j,n,t;
	scanf("%d",&t);
	while(t--)
	{
		tot=0;
		memset(word,0,sizeof(word));
		for (i=0; i<26; i++)
			tree[0][i]=0;
		scanf("%d",&n);
		string s;
		while(n--)
		{
			//scanf("%s",s);
			cin>>s;
			insert(s);
		}
		fail[0]=0;
		bfs();
		//scanf("%s",s);
		cin>>s;
		printf("%d\n",query(s));
	}
	return 0;
}

更新一下基本思路吧
总共三个主要函数,开始一个是insert,插入多个字符串,目的是构建一棵字典树
然后是构建fail数组,使用bfs,和kmp的next数组有点类似。
最后是query函数,输入待查询的字符串,利用tree[][]数组和fail[]数组以及for(j=u; j&&word[j]!=-1; j=fail[j]){ans+=word[j];word[j]=-1;}
进行查询。

1.字典树,构建tir树:
字典树的构建过程是这样的,当要插入许多单词的时候,我们要从前往后遍历整个字符串,
当我们发现当前要插入的字符其节点再先前已经建成,我们直接去考虑下一个字符即可,
当我们发现当前要插入的字符没有
再其前一个字符所形成的树下没有自己的节点,我们就要创建一个新节点来表示这个字符,
接下往下遍历其他的字符。然后重复上述操作。

2.找Fail指针,解决失配问题
当发现失配的字符失配的时候,跳转到fail指针指向的位置,
然后再次进行匹配操作,AC自动机之所以能实现多模式匹配,就归功于Fail指针的建立。
当前节点t有fail指针,其fail指针所指向的节点和t所代表的字符是相同的。
因为t匹配成功后,我们需要去匹配t->child,发现失配,那么就从t->fail这个节点开始再次去进行匹配。

求法:
Fail指针的求法:
Fail指针用BFS来求得,对于直接与根节点相连的节点来说,如果这些节点失配,
他们的Fail指针直接指向root即可,
其他节点其Fail指针求法如下:
假设当前节点为father,其孩子节点记为child。求child的Fail指针时,
首先我们要找到其father的Fail指针所指向的节点,假如是t的话,
我们就要看t的孩子中有没有和child节点所表示的字母相同的节点,如果有的话,
这个节点就是child的fail指针,如果发现没有,则需要找father->fail->fail这个节点,
然后重复上面过程,如果一直找都找不到,则child的Fail指针就要指向root。

3.最后一步,文本串匹配
1)当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符,
如果当前匹配的字符是一个单词的结尾,我们可以沿着当前字符的fail指针,
一直遍历到根,如果这些节点末尾有标记(此处标记代表,节点是一个单词末尾的标记),
这些节点全都是可以匹配上的节点。
我们统计完毕后,并将那些节点标记。此时只需沿该路径走向下一个节点继续匹配即可,
目标字符串指针移向下个字符继续匹配;
2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。
重复这2个过程中的任意一个,直到模式串走到结尾为止。

posted @ 2020-11-14 20:55  索饮  阅读(125)  评论(0编辑  收藏  举报