程帅霞

不断受挫,不停起身,不断追寻,不止AC~~

导航

AC自动机讲解

三步走:

①将所有的模式串构成一颗Trie树。

②对Trie上所有的节点构造前缀指针。

③利用前缀指针Fail对主串进行匹配。

实际上这个前缀指针Fail与KMP算法中的nxt数组非常相似,因此AC自动机可以看作是Trie与KMP算法的结合(Trie上的KMP算法)。

   第一步:字典树的构建.

   第二步:

    找Fail指针:

     运用广度优先搜索 (BFS)来求得。

     对于与根节点直接相连的点来说,如果这些节点失配,他们的Fail指针直接指向root即可。

    其他节点其Fail指针求法如下:
    设当前节点为father,其子节点为child。

     求child的Fail指针时,首先我们要找到其father的Fail指针所指向的节点设为F,看F的孩子中有没有和child节点所表示的字母相同的节点,如果有的话,这个节

    点就是child的Fail指针,如果没有,则需要再找到F的Fail指针所指向的节点,如果一直找都找不到,则child的Fail指针就要指向root。

第三步:

文本串的匹配:

匹配过程分两种情况:

(1)当前字符匹配,从当前节点沿着树边有一条路径可以到达目标字符,如果当前匹配的字符是一个单词的结尾,就沿着当前字符的Fail指针,一直遍历到根,如果这些节点末尾有标记(当前节点单词末尾的标记),这些节点全都是可以匹配上的节点。统计完毕后,并将那些节点标记。此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;

(2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,当指针指向root时结束。

重复这2个过程中的任意一个,直到模式串走到结尾为止。

eg:

题目传送门

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n;
 4 char s[2000001];
 5 int trie[1000001][30];
 6 int que[1000001],end[1000001],nxt[1000001];
 7 int ans,cnt;
 8 void insert(char *str)//Trie树构建过程 
 9 {
10     int p=1;
11     int len=strlen(str);
12     for(int i=0;i<len;i++)
13     {
14         int ch=str[i]-'a';
15         if(!trie[p][ch])
16         {
17             trie[p][ch]=++cnt;
18             memset(trie[cnt],0,sizeof(trie[cnt]));//每次只需要清空我们会用得到的行 
19         }
20         p=trie[p][ch];
21     }
22     end[p]++;//因为有可能会有重复的单词,故在此end统计在此有多少个单词结束,而不是有没有单词结束 
23 }
24 void build()//BFS构建Fail指针 
25 {
26     for(int i=0;i<26;i++)//为了方便将0的所有转一遍都设为根节点1 
27         trie[0][i]=1;
28     nxt[1]=0;//若在根节点失配, 则无法匹配字符 
29     que[1]=1;
30     int head=1,tail=1;
31     while(head<=tail)
32     {
33         for(int i=0;i<26;i++)
34             if(!trie[que[head]][i])trie[que[head]][i]=trie[nxt[que[head]]][i];//注意这里,下面会有详细解释 
35             else
36             {
37                 que[++tail]=trie[que[head]][i];
38                 int flag=nxt[que[head]];
39                 while(flag&&!trie[flag][i])flag=nxt[flag];//循环往前找 
40                 nxt[trie[que[head]][i]]=trie[nxt[que[head]]][i];
41             }
42         head++;//注意队头++ 
43     }
44 }
45 void find(char *str)//匹配 
46 {
47     int p=1;
48     int len=strlen(str);
49     for(int i=0;i<len;i++)
50     {
51         int flag=p=trie[p][str[i]-'a'];
52         while(end[flag]!=-1&&flag)
53         {
54             ans+=end[flag];
55             end[flag]=-1;//标记这个点已经访问过,以后不再访问 
56             flag=nxt[flag];
57         }
58     }
59 }
60 int main()
61 {
62     int T;
63     scanf("%d",&T);
64     while(T--)
65     {
66         memset(end,0,sizeof(end));//多测不清空,爆零两行泪(宝宝别哭) 
67         cnt=1;
68         ans=0;
69         for(int i=0;i<26;i++)
70             trie[0][i]=1,trie[1][i]=0;//亦是清空 
71         scanf("%d",&n);
72         for(int i=1;i<=n;i++)
73         {
74             scanf("%s",s);
75             insert(s);//读入子串并插入Trie树 
76         }
77         build();
78         scanf("%s",s);
79         find(s);//匹配 
80         printf("%d\n",ans);
81     }
82     return 0;
83 }

 

posted on 2020-11-03 20:45  程帅霞  阅读(111)  评论(0)    收藏  举报