Keywords Search(多模式串字符匹配--我的第一道AC自动机)

来源:互联网 发布:tt聊天软件 编辑:程序博客网 时间:2024/06/05 11:49


Link:http://acm.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): 44509    Accepted Submission(s): 14021


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
 

Author
Wiskey
 


有关AC自动机算法的入门讲解见博客: http://blog.csdn.net/u014634338/article/details/47126565


AC  code:

#include <algorithm>#include <iostream>#include <iomanip>#include <cstring>#include <climits>#include <complex>#include <fstream>#include <cassert>#include <cstdio>#include <bitset>#include <vector>#include <deque>#include <queue>#include <stack>#include <ctime>#include <set>#include <map>#include <cmath>#define LL long long#define MAXN 1000010 using namespace std;const int mod=258280327;const int N=500010;//字典树最大容纳的结点(字符)数目 int trie[N][28];#define fail 27 //失配指针#define count 26 //计数器 #define root 0 //根结点指针int q[N];//队列int cnt=1; //结点(代表一个字符)个数计数器  void BuildTrie(char *str){int len=strlen(str);int j=0;int cur=root;for(int i=0;i<len;i++){j=str[i]-'a';if(trie[cur][j]==0){memset(trie[cnt],0,sizeof(trie[cnt]));//新增一个结点(加入一个字符) trie[cur][j]=cnt++;}cur=trie[cur][j];}trie[cur][count]++;//以结点cur结尾的单词个数 }void BuildAC(){int front=0,rear=0;q[rear++]=root;trie[root][fail]=root;//根结点指向自己int cur,temp;while(front!=rear){cur=q[front++];for(int i=0;i<26;i++)//trie[cur][i]为结点cur(父亲结点)的某个儿子结点 {if(trie[cur][i]!=0)//字典树里结点cur(父亲结点)对应的字符存在下一个字符a+i(儿子结点)与其相连 {if(cur==root)//如果父亲结点是根,那么失败就只能返回根 {trie[trie[cur][i]][fail]=root;//trie[trie[cur][i]][fail]为儿子结点trie[cur][i]的失配指针  } else{temp=trie[cur][fail]; //trie[cur][i]的父亲cur的失配指针temp while(temp!=0 && !trie[temp][i]) //找到一个和trie[cur][i]字符相同的结点所处的位置  {/*构造失败指针的过程:在Trie树上当匹配到节点上的字母为x无法继续匹配,那么就沿着他父亲的失败指针走,直到走到一个节点,他的儿子中也有字母为x的节点。然后把当前节点的失败指针指向那个字符也为x的儿子。如果一直走到了root都没找到,那就把失败指针指向root。*/ temp=trie[temp][fail];//迭代查找 }trie[trie[cur][i]][fail]=trie[temp][i];//将trie[cur][i]的失败指针指向相同的点,这里更新的是父亲结点的失配指针(目前还不是很理解)   } q[rear++]=trie[cur][i];}else{trie[cur][i]=trie[trie[cur][fail]][i];//不存在这一点,指向父亲结点失配指针的第i个儿子}}}}int query(char *str){int j;int ans=0;int len=strlen(str);int cur=root;int temp;for(int i=0;i<len;i++){j=str[i]-'a';cur=trie[cur][j];temp=cur;while(temp!=0 && trie[temp][count]!=-1){ans+=trie[temp][count];trie[temp][count]=-1;temp=trie[temp][fail];}}return ans;}char s[MAXN];int main(){int t,n;scanf("%d",&t);while(t--){scanf("%d",&n);memset(trie[0],0,sizeof(trie[0]));cnt=1;for(int i=0;i<n;i++){scanf("%s",s);//printf("%s\n",s);BuildTrie(s);}BuildAC();scanf("%s",s);printf("%d\n",query(s));}return 0; } 

升级版:

#include <algorithm>#include <iostream>#include <iomanip>#include <cstring>#include <climits>#include <complex>#include <fstream>#include <cassert>#include <cstdio>#include <bitset>#include <vector>#include <deque>#include <queue>#include <stack>#include <ctime>#include <set>#include <map>#include <cmath>#define LL long long#define MAXN 1000010 using namespace std;struct Trie{int next[210*2500][128];//有128个可显字符(若题目给的只有小写字母,128可能会暴),还有,注意第一个下标容量大小!!!//防止MLE或RE(access_violation),下面开的数组大小类似上面的数组第一个下标容量大小,//第一个下标表示字典树最大结点数,也就是允许的最大总的字符总数 int fail[210*2500];//失配指针  int end[210*2500];//记录数组,若要输出出现模式串的id,用end记录idint count[210*2500];//计数器 int root;//根结点指针 int L;//总长度int NewNode() //获取新结点并初始化  {for(int i=0;i<128;i++){next[L][i]=-1;}end[L]=-1;count[L]=0;return L++;}void init() //初始化{L=0;root=NewNode();}void Insert(char *s,int id){int len=strlen(s);int j=root;for(int i=0;i<len;i++){if(next[j][s[i]]==-1)//不存在该结点{next[j][s[i]]=NewNode();}j=next[j][s[i]];}end[j]=id;//记录其id  count[j]++;}void build(){queue<int>q;fail[root]=root;//根结点失配指针指向自己  //根结点的孩子入队,其失配指针指向自己for(int i=0;i<128;i++){if(next[root][i]==-1)//不存在该孩子 {next[root][i]=root;//指向自己}else{fail[next[root][i]]=root;//失配指针指向自己  q.push(next[root][i]); //孩子入队  }}int j;while(!q.empty()){j=q.front();q.pop();for(int i=0;i<128;i++){if(next[j][i]==-1)//不存在该孩子,指向其父结点失配指针所指向的结点(该结点也有孩子i)  {next[j][i]=next[fail[j]][i];}else{fail[next[j][i]]=next[fail[j]][i];q.push(next[j][i]);}}}}bool used[2510];//记录数组  int query(char *str,int n,int id){int len=strlen(str);int j=root;int temp;int ans1=0;//how many keywords will be match,即求目标串中出现了几个模式串(同一个模式串允许在源串的不同位置重复出现,出现一次,次数累加一次) int ans2=0;//出现的模式串个数(同一个模式串不允许在源串的不同位置重复出现,即使出现多次,次数只算一次)  memset(used,false,sizeof(used));for(int i=0;i<len;i++){j=next[j][str[i]];temp=j;while(temp!=root){ans1+=count[temp];count[temp]=0;if(end[temp]!=-1) //该单词或字符在Trie中出现了 {used[end[temp]]=true;//记录ans2++;}temp=fail[temp];//继续找后缀串}}/*if(ans2>0)//按字典序输出已出现过的模式串 {printf("web %d:",id);for(int i=1;i<=n;i++){if(used[i])printf(" %d",i);}printf("\n");}*/return ans1;//返回目标串中出现的模式串个数(出现多次,次数累加算) //return ans2;//返回目标串中已出现过的模式串个数(出现多次,次数只算一次)}};char str[MAXN];Trie ac;int main(){    int n,m,tot,t;    scanf("%d",&t);    while(t--)    {    scanf("%d",&n);    ac.init();    for(int i=1;i<=n;i++)    {    scanf("%s",str);    ac.Insert(str,i);}ac.build();scanf("%s",str);tot=ac.query(str,n,1);printf("%d\n",tot);}    /*while(scanf("%d",&n)!=EOF)    {    ac.init();    for(int i=1;i<=n;i++)    {    scanf("%s",str);    ac.Insert(str,i);}ac.build();tot=0;scanf("%d",&m);for(int i=1;i<=m;i++){scanf("%s",str);if(ac.query(str,n,i)>0)tot++;}printf("total: %d\n",tot);}*/    return 0;}

注意:上面的升级版不知为何,模板里多加了一个与此题无关的memset(used,false,sizeof(used));就TLE了,所以要注释掉才能AC

AC code:

#include <algorithm>#include <iostream>#include <iomanip>#include <cstring>#include <climits>#include <complex>#include <fstream>#include <cassert>#include <cstdio>#include <bitset>#include <vector>#include <deque>#include <queue>#include <stack>#include <ctime>#include <set>#include <map>#include <cmath>#define LL long long#define MAXN 1000010 using namespace std;struct Trie{    int next[500001][26];//有128个可显字符(若题目给的只有小写字母,128可能会暴),还有,注意第一个下标容量大小!!!    //防止MLE或RE(access_violation),下面开的数组大小类似上面的数组第一个下标容量大小,    //第一个下标表示字典树最大结点数,也就是允许的最大总的字符总数     int fail[500001];//失配指针      int end[500001];//记录数组,若要输出出现模式串的id,用end记录id    int count[500001];//计数器         int root;//根结点指针     int L;//总长度    int NewNode() //获取新结点并初始化      {        for(int i=0;i<26;i++)        {            next[L][i]=-1;        }        end[L]=-1;        count[L]=0;        return L++;    }    void init() //初始化    {        L=0;        root=NewNode();    }    void Insert(char *s,int id)    {        int len=strlen(s);        int j=root;        for(int i=0;i<len;i++)        {            if(next[j][s[i]-'a']==-1)//不存在该结点            {                next[j][s[i]-'a']=NewNode();            }            j=next[j][s[i]-'a'];        }        end[j]=id;//记录其id          count[j]++;//一定要注意出现的单词可能重复,所以必然是++     }    void build()    {        queue<int>q;        fail[root]=root;//根结点失配指针指向自己          //根结点的孩子入队,其失配指针指向自己        for(int i=0;i<26;i++)        {            if(next[root][i]==-1)//不存在该孩子             {                next[root][i]=root;//指向自己            }            else            {                fail[next[root][i]]=root;//失配指针指向自己                  q.push(next[root][i]); //孩子入队              }        }        int j;        while(!q.empty())        {            j=q.front();            q.pop();            for(int i=0;i<26;i++)            {                if(next[j][i]==-1)//不存在该孩子,指向其父结点失配指针所指向的结点(该结点也有孩子i)                  {                    next[j][i]=next[fail[j]][i];                }                else                {                    fail[next[j][i]]=next[fail[j]][i];                    q.push(next[j][i]);                }            }        }    }    //bool used[10010];//记录数组      int query(char *str,int n,int id)    {        int len=strlen(str);        int j=root;        int temp;        int ans1=0;//how many keywords will be match,即求目标串中出现了几个模式串(同一个模式串允许在源串的不同位置重复出现,出现一次,次数累加一次)         //memset(used,false,sizeof(used));        for(int i=0;i<len;i++)        {            j=next[j][str[i]-'a'];            temp=j;            while(temp!=root)            {                ans1+=count[temp];                count[temp]=0;//注意:这里是否要注释掉要看题目问的是什么,如果是题目是求给出的所有模式串中有几个在源串出现过,则不能注释掉。如果是//求所有模式串在源串中出现的次数总和(且同一个模式串匹配源串的位置可部分重叠)则必须注释掉,上面定义ans1的注释是针对这种情况的                 if(end[temp]!=-1)//该单词或字符在Trie中出现了                 {                    //used[end[temp]]=true;//记录                }                temp=fail[temp];//继续找后缀串            }        }        /*if(ans1>0)//按字典序输出已出现过的模式串         {            printf("web %d:",id);            for(int i=1;i<=n;i++)            {                if(used[i])                    printf(" %d",i);            }            printf("\n");        }*/        return ans1;//返回目标串中已出现过的模式串个数(出现多次,次数只算一次)    }};char str[MAXN];Trie ac;int main(){    int n,m,tot,t;    scanf("%d",&t);    while(t--)    {        scanf("%d",&n);        ac.init();        for(int i=1;i<=n;i++)        {            scanf("%s",str);            ac.Insert(str,i);        }        ac.build();        scanf("%s",str);        tot=ac.query(str,n,1);        printf("%d\n",tot);    }    /*while(scanf("%d",&n)!=EOF)    {        ac.init();        for(int i=1;i<=n;i++)        {            scanf("%s",str);            ac.Insert(str,i);        }        ac.build();        tot=0;        scanf("%d",&m);        for(int i=1;i<=m;i++)        {            scanf("%s",str);            if(ac.query(str,n,i)>0)                tot++;        }        printf("total: %d\n",tot);    }*/    return 0;}/*附上测试数据和测试结果加以理解*//*输入样例 16shehesayshrherhyasherhs 不注释掉count[temp]=0;这句代码时:输出样例 4注释掉count[temp]=0;这句代码时:输出样例 5样例输出结果解释:注释掉的话,文本中若有重复的病毒,则会多增加不必要的次数,如h次数多加了一次,所以是5,应该输出4的才对,但是我文本中有两个h,所以输出5,样例中没有可以在文本中重复的病毒,所以不注释掉才是正确的 */  


0 0