AC自动机--------hdu 2222

来源:互联网 发布:附加遗产网络剧百度云 编辑:程序博客网 时间:2024/06/05 18:11


AC自动机简介: 
AC自动机:Aho-Corasick automation,是著名的多模匹配算法之一。一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。


AC自动机的构造:

1.构造一棵Trie,作为AC自动机的搜索数据结构。

2.构造fail指针,使当前字符失配时跳转到具有最长公共前后缀的字符继续匹配。如同 KMP算法一样, AC自动机在匹配时如果当前字符匹配失败,那么利用fail指针进行跳转。由此可知如果跳转,跳转后的串的前缀,必为跳转前的模式串的后缀并且跳转的新位置的深度(匹配字符个数)一定小于跳之前的节点。所以我们可以利用 bfs在 Trie上面进行 fail指针的求解。

3.扫描主串进行匹配。

AC自动机详讲:

我们给出5个单词,say,she,shr,he,her。给定字符串为yasherhs。问多少个单词在字符串中出现过。

一、Trie

首先我们需要建立一棵Trie。但是这棵Trie不是普通的Trie,而是带有一些特殊的性质。

首先会有3个重要的指针,分别为p, p->fail, temp。

1.指针p,指向当前匹配的字符。若p指向root,表示当前匹配的字符序列为空。(root是Trie入口,没有实际含义)。

2.指针p->fail,p的失败指针,指向与字符p相同的结点,若没有,则指向root。

3.指针temp,测试指针,在建立fail指针时有寻找与p字符匹配的结点的作用。

对于Trie树中的一个节点,对应一个序列s[1...m]。此时,p指向字符s[m]。若在下一个字符处失配,即p->next[s[m+1]] == NULL,则由失配指针跳到另一个节点(p->fail)处,该节点对应的序列为s[i...m]。若继续失配,则序列依次跳转直到序列为空或出现匹配。在此过程中,p的值一直在变化,但是p对应节点的字符没有发生变化。在此过程中,我们观察可知,最终求得得序列s则为最长公共后缀。另外,由于这个序列是从root开始到某一节点,则说明这个序列有可能是某些序列的前缀。

再次讨论p指针转移的意义。如果p指针在某一字符s[m+1]处失配(即p->next[s[m+1]] == NULL),则说明没有单词s[1...m+1]存在。此时,如果p的失配指针指向root,则说明当前序列的任意后缀不会是某个单词的前缀。如果p的失配指针不指向root,则说明序列s[i...m]是某一单词的前缀,于是跳转到p的失配指针,以s[i...m]为前缀继续匹配s[m+1]。

对于已经得到的序列s[1...m],由于s[i...m]可能是某单词的后缀,s[1...j]可能是某单词的前缀,所以s[1...m]中可能会出现单词。此时,p指向已匹配的字符,不能动。于是,令temp = p,然后依次测试s[1...m], s[i...m]是否是单词。

构造的Trie为:


二、构造失败指针

用BFS来构造失败指针,与KMP算法相似的思想。

首先,root入队,第1次循环时处理与root相连的字符,也就是各个单词的第一个字符h和s,因为第一个字符不匹配需要重新匹配,所以第一个字符都指向root(root是Trie入口,没有实际含义)失败指针的指向对应下图中的(1),(2)两条虚线;第2次进入循环后,从队列中先弹出h,接下来p指向h节点的fail指针指向的节点,也就是root;p=p->fail也就是p=NULL说明匹配序列为空,则把节点e的fail指针指向root表示没有匹配序列,对应图-2中的(3),然后节点e进入队列;第3次循环时,弹出的第一个节点a的操作与上一步操作的节点e相同,把a的fail指针指向root,对应图-2中的(4),并入队;第4次进入循环时,弹出节点h(图中左边那个),这时操作略有不同。由于p->next[i]!=NULL(root有h这个儿子节点,图中右边那个),这样便把左边那个h节点的失败指针指向右边那个root的儿子节点h,对应图-2中的(5),然后h入队。以此类推:在循环结束后,所有的失败指针就是图-2中的这种形式。


三、扫描

构造好Trie和失败指针后,我们就可以对主串进行扫描了。这个过程和KMP算法很类似,但是也有一定的区别,主要是因为AC自动机处理的是多串模式,需要防止遗漏某个单词,所以引入temp指针。

匹配过程分两种情况:(1)当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符,此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;(2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。重复这2个过程中的任意一个,直到模式串走到结尾为止。

 对照上图,看一下模式匹配这个详细的流程,其中模式串为yasherhs。对于i=0,1。Trie中没有对应的路径,故不做任何操作;i=2,3,4时,指针p走到左下节点e。因为节点e的count信息为1,所以cnt+1,并且讲节点e的count值设置为-1,表示改单词已经出现过了,防止重复计数,最后temp指向e节点的失败指针所指向的节点继续查找,以此类推,最后temp指向root,退出while循环,这个过程中count增加了2。表示找到了2个单词she和he。当i=5时,程序进入第5行,p指向其失败指针的节点,也就是右边那个e节点,随后在第6行指向r节点,r节点的count值为1,从而count+1,循环直到temp指向root为止。最后i=6,7时,找不到任何匹配,匹配过程结束。


hdu 2222


Problem Description
In the modern time, Search engine came into the life of everybody like Google, Baidu, etc.
Wiskey also wants to bring this feature to his image retrieval system.
Every image have a long description, when users type some keywords to find the image, the system will match the keywords with description of image and show the image which the most keywords be matched.
To simplify the problem, giving you a description of image, and some keywords, you should tell me how many keywords will be match.

Input
First line will contain one integer means how many cases will follow by.
Each case will contain two integers N means the number of keywords and N keywords follow. (N <= 10000)
Each keyword will only contains characters 'a'-'z', and the length will be not longer than 50.
The last line is the description, and the length will be not longer than 1000000.

Output
Print how many keywords are contained in the description.

Sample Input
15shehesayshrheryasherhs

Sample Output
3
#include<iostream>#include<cstdio>#include<cstring>#include<string>#include<vector>#include<queue>#include<set>#include<algorithm>#include<cstdlib>#define LL long long#define inf 0x3f3f3f3fusing namespace std;const int MAXN=1000010;struct trienode{    int ncount;//是否为单词最后一个结点,如果不是只贡献0    trienode *next[26+5];    trienode *fail;//失败指针};char str[MAXN];//匹配穿char key[60];//多个模式串void inserttrie(trienode* &root,char* key)//将字符串key放入字段树{    trienode *p=root;    int len=strlen(key);    for(int i=0;i<=len-1;i++)    {        int k=key[i]-'a';        if(p->next[k]==NULL)        {            p->next[k]=(struct trienode*)malloc(sizeof(struct trienode));            p->next[k]->ncount=0;            p->next[k]->fail=NULL;            for(int j=0;j<=25;j++)                p->next[k]->next[j]=NULL;        }        p=p->next[k];    }    p->ncount++;//单词结尾,数量+1}void buildfail(trienode *&root)//用bfs建立失配指针{    queue<trienode*> q;    q.push(root);    while(!q.empty())    {        trienode* temp=q.front();        q.pop();        for(int i=0;i<=25;i++)            if(temp->next[i])        {            if(temp==root)                temp->next[i]->fail=root;            else            {//兄弟结点之间不能建立失败指针,所以不断回溯,如果该结点的            //父节点的失败指针对应的结点next[i]存在,则将这个结点的失败指针连到这里            //回溯到root都没有,则结点的失败指针直接指向root                trienode *p=temp->fail;                while(p)                {                    if(p->next[i])                    {                        temp->next[i]->fail=p->next[i];                        break;                    }                    p=p->fail;                }                if(!p)                    temp->next[i]->fail=root;            }            q.push(temp->next[i]);        }    }}int query(trienode *&root){    trienode *p=root;    int cnt=0;//单词出现的个数    int len=strlen(str);    for(int i=0;i<=len-1;i++)    {        int k=str[i]-'a';        while(!p->next[k]&&p!=root)//失败指针回溯查找,如果没有则p=root            p=p->fail;        p=p->next[k];        if(!p)            p=root;        trienode *temp=p;        while(temp!=root)        {            if(temp->ncount>=0)//判断是否被访问,            {                 cnt+=temp->ncount;                 temp->ncount=-1;//表示已被访问            }            else                break;            temp=temp->fail;        }    }    return cnt;}int main(){    int T;    scanf("%d",&T);    while(T--)    {        trienode *root=(struct trienode*)malloc(sizeof(struct trienode));        root->ncount=0;        root->fail=NULL;        for(int i=0;i<=25;i++)            root->next[i]=NULL;        int n;        scanf("%d",&n);        getchar();        while(n--)        {            gets(key);            inserttrie(root,key);        }        buildfail(root);//在字典树上建立失败指针        gets(str);        cout<<query(root)<<endl;    }    return 0;}




0 0
原创粉丝点击