zoukankan      html  css  js  c++  java
  • 学习笔记:AC自动机

    话说AC自动机有什么用......我想要自动AC机

    AC自动机简介: 

    首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一。一个常见的例子就是给出n个单词,再给出一段包含m个字符的文 章,让你找出有多少个单词在文章里出现过。要搞懂AC自动机,先得有字典树Trie和KMP模式匹配算法的基础知识。KMP算法是单模式串的字符匹配算 法,AC自动机是多模式串的字符匹配算法。

    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时,找不到任何匹配,匹配过程结束。

    到此,AC自动机入门知识就讲完了。HDU 2222入门题必须果断A掉。bzoj3172也要A。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<cmath>
     5 #include<algorithm>
     6 #include<queue>
     7 #include<cstdlib>
     8 #include<iomanip>
     9 #include<cassert>
    10 #include<climits>
    11 #include<vector>
    12 #include<list>
    13 #define maxn 1000001
    14 #define F(i,j,k) for(int i=j;i<=k;i++)
    15 #define M(a,b) memset(a,b,sizeof(a))
    16 #define FF(i,j,k) for(int i=j;i>=k;i--)
    17 #define inf 0x7fffffff
    18 #define maxm 2016
    19 #define mod 1000000007
    20 //#define LOCAL
    21 using namespace std;
    22 int read(){
    23     int x=0,f=1;char ch=getchar();
    24     while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    25     while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    26     return x*f;
    27 }
    28 int pos[maxn];
    29 struct AC_automation
    30 {
    31     int cnt;
    32     int next[maxn][26],sum[maxn],fail[maxn],q[maxn];
    33     char ch[maxn];
    34     AC_automation()
    35     {
    36         cnt=1;
    37         F(i,0,25) next[0][i]=1;
    38     }
    39     void insert(int &pos)
    40     {
    41         int now=1;
    42         cin>>ch;
    43         int len=strlen(ch)-1;
    44         F(i,0,len){
    45             if(!next[now][ch[i]-'a']) next[now][ch[i]-'a']=++cnt;
    46             now=next[now][ch[i]-'a'];
    47             sum[now]++;
    48         }
    49         pos=now;
    50     }
    51     void build_fail()
    52     {
    53         int head=0,tail=1;
    54         q[0]=1;
    55         fail[1]=0;
    56         while(head<tail)
    57         {
    58             int now=q[head];
    59             head++;
    60             F(i,0,25){
    61                 int v=next[now][i];
    62                 if(!v) continue;
    63                 int k=fail[now];
    64                 while(!next[k][i]) k=fail[k];
    65                 fail[v]=next[k][i];
    66                 q[tail++]=v;
    67             }
    68         }
    69         FF(i,tail-1,0){
    70             sum[fail[q[i]]]+=sum[q[i]];
    71         }
    72     }
    73 }ac;
    74 long long n,m;
    75 int main()
    76 {
    77     std::ios::sync_with_stdio(false);//cout<<setiosflags(ios::fixed)<<setprecision(1)<<y;
    78     #ifdef LOCAL
    79     freopen("data.in","r",stdin);
    80     freopen("data.out","w",stdout);
    81     #endif
    82     cin>>n;
    83     F(i,1,n){
    84         ac.insert(pos[i]);
    85     }
    86     ac.build_fail();
    87     F(i,1,n){
    88         cout<<ac.sum[pos[i]]<<endl;
    89     }
    90     return 0;
    91 }
    bzoj 3172
  • 相关阅读:
    Python-HTML基础
    异常处理
    反射hasattr; getattr; setattr; delattr
    Python 属性方法、类方法、静态方法、 特殊属性__doc__ (内建属性)
    Python3 day6面向对象
    re模块计算器作业
    re正则表达式:import re ;re.search()
    hashlib模块学习:hmac
    ConfigParser模块,主要应用于对php.ini等格式的配置文件内容读取和生成。删改较少用
    ymal文档格式 处理
  • 原文地址:https://www.cnblogs.com/SBSOI/p/5681998.html
Copyright © 2011-2022 走看看