题干:
给定 n 个长度不超过 50 的由小写英文字母组成的单词准备查询,以及一篇长为m的文章,问:文中出现了多少个待查询的单词。多组数据
题解:
这是一道AC自动机的裸题(尽管你可以用hash+卡常A掉它。。。)。
AC自动机其实也就两个核心部分:fail指针(或数组)、trie图
1、fail指针(或数组)
其实AC自动机类似于KMP算法,由KMP的单模式匹配优化为了多模式匹配。在KMP中,我们维护了一个Next[]数组(注意在NOIP中,next为保留字),他记录的就是当这一位失配后,最近的上一位匹配到的位置。而AC自动机也沿袭了这一思想,出现了fail数组(或fail指针),用来指向其它可与目标串部分匹配(至少从fail指向的那个节点开始,到根都是匹配的)的另一个串的一个节点。
2、trie图
trie图是在trie树基础上为了降低时间复杂度而出现一种技巧(可能你手中的模板就是trie图的。。。)。它的核心就是当x没有y这个儿子时,将x节点的不存在的儿子节点y,用fail[x]的对应儿子节点y'补齐,这样就在查询时节省了再跑回根节点在匹配的不必要的跑路时间。因为在连边(补齐儿子)的过程中,我们将树的性质破坏,变成了图,我们就将它叫做trie图。
Code
下面附上个人代码,不要随便颓代码。。。
1 #include
2 #include
3 #include
4 #define up(i,x,y,z) for(register int i=(x);i<=(y);i+=(z))
5 #define down(i,x,y,z) for(register int i=(x);i>=(y);i-=(z))
6 #define $ 511111
7 using namespace std;
8 char str[$*2];
9 int m,n,tot,t;
10 struct trie{
11 int next[$][26],fail[$],end[$],count[$],root,length;
12 inline int NewTrie(){
13 up(i,0,25,1) next[length][i]=-1;
14 end[length]=-1;
15 count[length]=0;
16 return length++;
17 }
18 inline void init(){ length=0; root=NewTrie(); }
19 inline void insert(char *s,int id){
20 int len=strlen(s);
21 int x=root;
22 up(i,0,len-1,1){
23 if(next[x][s[i]-'a']==-1) next[x][s[i]-'a']=NewTrie();
24 x=next[x][s[i]-'a'];
25 }
26 end[x]=id;
27 count[x]++;
28 }
29 inline void build(){
30 queue<int> q;
31 fail[root]=root;
32 up(i,0,25,1){
33 if(next[root][i]==-1) next[root][i]=root;
34 else fail[next[root][i]]=root,q.push(next[root][i]);
35 }
36 while(q.size()){
37 int x=q.front(); q.pop();
38 up(i,0,25,1){
39 if(next[x][i]==-1) next[x][i]=next[fail[x]][i];
40 else fail[next[x][i]]=next[fail[x]][i],q.push(next[x][i]);
41 }
42 }
43 }
44
45 inline int query(char *str,int n,int id){
46 int len=strlen(str),x=root,ans=0,temp;
47 up(i,0,len-1,1){
48 x=next[x][str[i]-'a'];
49 temp=x;
50 while(temp!=root){
51 ans+=count[temp];
52 count[temp]=0;
53 temp=fail[temp];
54 }
55 }
56 return ans;
57 }
58 }AC;
59 signed main(){
60 scanf("%d",&t);
61 while(t--){
62 scanf("%d",&n);
63 AC.init();
64 up(i,1,n,1) scanf("%s",str),AC.insert(str,i);
65 AC.build();
66 scanf("%s",str);
67 printf("%d\n",AC.query(str,n,1));
68 }
69 }
1 #include
2 #include
3 #include
4 #define $ 10000000
5 using namespace std;
6 int m,n,t;
7 char s[$],print[$];
8 struct trie{ int sum; trie *son[27],*fail; };///4096
9 inline trie *newnode(){
10 trie *p=new trie;
11 for(register int i=0;i<26;++i) p->son[i]=NULL;
12 p->fail=NULL; p->sum=0;
13 return p;
14 }
15 inline void insert(trie *root){
16 trie *p=root;
17 for(register int i=1;i<=strlen(s+1);++i){
18 int x=s[i]-'a';
19 if(p->son[x]==NULL) p->son[x]=newnode();
20 p=p->son[x];
21 }
22 p->sum++;
23 }
24 inline void build(trie *root){
25 queue q;
26 for(register int i=0;i<26;++i){
27 if(root->son[i]) root->son[i]->fail=root,q.push(root->son[i]);
28 else root->son[i]=root;
29 }
30 while(q.size()){
31 trie *p=q.front(); q.pop();
32 for(register int i=0;i<26;++i){
33 if(p->son[i]){
34 p->son[i]->fail=p->fail->son[i];
35 q.push(p->son[i]);
36 }
37 else p->son[i]=p->fail->son[i];
38 }
39 }
40 }
41 inline void ac(trie *root){
42 int ans=0,len=strlen(s+1);
43 trie *p=root;
44 for(register int i=1;i<=len;++i){
45 int x=s[i]-'a';
46 while(p->son[x]==NULL&&p!=root) p=p->son[x];
47 p=p->son[x];
48 trie *pp=p;
49 while(pp->sum!=-1&&pp!=root){
50 ans+=pp->sum;
51 pp->sum=-1;
52 pp=pp->fail;
53 }
54 }
55 printf("%d\n",ans);
56 }
57 inline void work(){
58 trie *root=newnode();
59 scanf("%d",&n);
60 for(register int i=1;i<=n;++i) scanf("%s",s+1),insert(root);
61 build(root);
62 scanf("%s",s+1); ac(root);
63 }
64 signed main(){
65 scanf("%d",&t);
66 while(t--) work();
67 }