自动机(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个过程中的任意一个,直到模式串走到结尾为止。