题目
3172: [Tjoi2013]单词
Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 3655 Solved: 1757
[Submit][Status][Discuss]
Description
某人读论文,一篇论文是由许多单词组成。但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次。
Input
第一个一个整数N,表示有多少个单词,接下来N行每行一个单词。每个单词由小写字母组成,N<=200,单词长度不超过10^6
Output
输出N个整数,第i行的数字表示第i个单词在文章中出现了多少次。
Sample Input
3
a
aa
aaa
a
aa
aaa
Sample Output
6
3
1
3
1
HINT
题解
首先很自然地想到暴力。。。。
直接进行多串匹配
可惜会TLE
附上代码:
//TLE代码.......... #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; struct Node { Node *fail; Node *next[27]; int id; Node() { fail = NULL; id = 0; memset(next,NULL,sizeof(next)); } }; char T[205][1000005]; int _cnt[1005]; int l[1005]; int belong[1005]; void buildTrie(char *str,Node *root,int id) { Node *p = root,*q; int len=l[id]; for(int i=0; i<len; i++) { int idx = str[i]-'a'; if(p->next[idx]==NULL) p->next[idx]=new Node(); p = p->next[idx]; } if(p->id==0) p->id = id; belong[id]=p->id; } void build_AC_automation(Node *root) { Node* q[200005]; int pi=1,h=0; root->fail = NULL; q[++h]=root; while(pi<=h) { Node *p = NULL; Node *temp = q[pi]; pi++; for(int i=0; i<27; i++) { if(temp->next[i]!=NULL) { if(temp==root) temp->next[i]->fail=root; else { p=temp->fail; while(p!=NULL) { if(p->next[i]!=NULL) { temp->next[i]->fail=p->next[i]; break; } p=p->fail; } if(p==NULL) temp->next[i]->fail=root; } q[++h]=temp->next[i]; } } } } void query(char *str,int li,Node *root) { Node *p = root; int len=li; for(int i=0; i<len; i++) { int idx = str[i]-'a'; while(p->next[idx]==NULL&&p!=root) p = p->fail; p = p->next[idx]; p = (p==NULL)?root:p; Node * temp = p; while(temp!=root) { if(temp->id!=0) _cnt[temp->id]++; temp = temp->fail; } } } int main() { int n; scanf("%d",&n); Node *root = new Node(); for(int i=1; i<=n; i++) { scanf("%s",T[i]); l[i]=strlen(T[i]); buildTrie(T[i],root,i); } build_AC_automation(root); for(int i=1;i<=n;i++) query(T[i],l[i],root); for(int i=1; i<=n; i++) printf("%d\n",_cnt[belong[i]]); return 0; }
其实认真的想一下,很容易想到,根据fail指针的定义,
假设有一个字符串A的的末尾为p,那么若p->fail是另一个单词B的末尾
那么字符串A必定包含单词B
so 对于每一个fail指针不为root且NULL的点p(其实root可以不管它)
p->fail->cnt+=p->cnt;
这就判断了全部后缀的情况
当然还要加上建立AC自动机时
经过每个单词末尾的次数。
附上AC代码
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; struct Node { Node *fail; Node *next[27]; int cnt; Node() { fail = NULL; cnt=0; memset(next,NULL,sizeof(next)); } }; Node *w[205]; char T[205][1000005]; int l[1005]; int belong[1005]; void buildTrie(char *str,Node *root,int id) { Node *p = root,*q; int len=l[id]; for(int i=0; i<len; i++) { int idx = str[i]-'a'; if(p->next[idx]==NULL) p->next[idx]=new Node(); p = p->next[idx]; p->cnt++; } w[id]=p; } void build_AC_automation(Node *root) { Node* q[200005]; int pi=1,h=0; root->fail = NULL; q[++h]=root; while(pi<=h) { Node *p = NULL; Node *temp = q[pi]; pi++; for(int i=0; i<27; i++) { if(temp->next[i]!=NULL) { if(temp==root) temp->next[i]->fail=root; else { p=temp->fail; while(p!=NULL) { if(p->next[i]!=NULL) { temp->next[i]->fail=p->next[i]; break; } p=p->fail; } if(p==NULL) temp->next[i]->fail=root; } q[++h]=temp->next[i]; } } } for(int i=h;i>=1;i--) if(q[i]->fail!=root&&q[i]->fail!=NULL) q[i]->fail->cnt+=q[i]->cnt; } int main() { int n; scanf("%d",&n); Node *root = new Node(); for(int i=1;i<=n;i++) { scanf("%s",T[i]); l[i]=strlen(T[i]); buildTrie(T[i],root,i); } build_AC_automation(root); for(int i=1; i<=n; i++) printf("%d\n",w[i]->cnt); return 0; }