• AC自动机


    Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。

    解决什么问题呢?

    KMP是给你一个模式串和一个文本串,要求求出模式串的匹配位置。

    而AC自动机是给你一个文本串和一堆模式串,问你能匹配上多少模式串。

    容易想到我们可以在Trie上跑KMP,结合二者的优点就是我们的AC自动机了。

    想学AC自动机,先要明白Trie树的实现方法和KMP的思想。

    AC自动机大概就长这个样,它是在字典树的基础上多了个失配指针,告诉你失配了该往哪里跑,就相当于KMP中的next数组。

    比如我们在K处失配

    显然next记录的位置是在k的父亲的next下的s[k]的编号

    我们的next是按照从根节点开始的BFS序求解,就是这样。

     1 void get_next()
     2 {
     3     for(int i=0;i<26;i++)
     4     {
     5         if(trie[0][i])
     6         {
     7             next[trie[0][i]]=0;
     8             q.push(trie[0][i]);
     9             //因为按照BFS序找next嘛,可以理解为什么按BFS序吧 
    10         }
    11     }//先预处理深度为1的点的next,显然他父亲没有与i相同的兄弟,所以直接赋0 
    12     while(!q.empty())
    13     {
    14         int cnt=q.front(); q.pop();//取队首,找他儿子的next 
    15         for(int i=0;i<26;i++)
    16         {
    17             if(trie[cnt][i])
    18             {
    19                 next[trie[cnt][i]]=trie[next[cnt]][i];//显然的转移 
    20                 q.push(trie[cnt][i]);
    21             }
    22             else
    23             trie[cnt][i]=trie[next[cnt]][i];//既然不存在,我们查找它时就应该找到上面那个i的位置
    24             //由于是按BFS序找的,所以 trie[next[cnt]][i]一定找过 
    25         }
    26     }
    27 }

    这就是next的求法。

     对了,忘了说了,建AC自动机的方法就是建一颗Trie:

     1 void _insert(string s)
     2 {
     3     int now=0;
     4     int size=s.size();
     5     for(int i=0;i<size;i++)
     6     {
     7         int x=s[i]-'a';
     8         if(!trie[now][x])
     9         trie[now][x]=++total;
    10         now=trie[now][x];
    11     }
    12     _word[now]++;//我们令_word记录以now结尾有几个单词,用于解题的。 
    13 }

    如果看官实在看不懂鄙人丑陋的码风,可以转开始的链接-Trie

    好,接下来就是查找了:

    给我们一个文本串S,问在AC自动机上有多少单词出现在了S上(多模式串匹配)

    我们的_word还是很有用的,就是我们每找到一个结尾k,那么就可以保证结尾为k的所有单词都被访问过一遍,所以我们的答案加上_word[k],之后为避免重复,将_word[k]清零。

    查找嘛,正常情况就按照查找字典树的办法查找,失配的话就跳到next,同时我们要保证你找到的一定是单词结尾(显然若不存在,程序结束)。

     1 int find(string s)
     2 {
     3     int size=s.size();
     4     int now=0,ans=0;
     5     for(int i=0;i<size;i++)
     6     {
     7         int x=s[i]-'a';
     8         now=trie[now][x];
     9         for(int k=now;k&&_word[k];k=next[k])
    10         {
    11             ans+=_word[k];
    12             _word[k]=0;
    13         }
    14     }
    15     return ans;
    16 }

    于是就结束了。

    //洛谷3808

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<string>
    #include<queue>
    using namespace std;
    int n,trie[1000010][30],next[1000010],total,_word[10000010];
    queue<int>q;
    void _insert(string s)
    {
        int now=0;
        int size=s.size();
        for(int i=0;i<size;i++)
        {
            int x=s[i]-'a';
            if(!trie[now][x])
            trie[now][x]=++total;
            now=trie[now][x];
        }
        _word[now]++;
    }
    void get_next()
    {
        for(int i=0;i<26;i++)
        {
            if(trie[0][i])
            {
                next[trie[0][i]]=0;
                q.push(trie[0][i]);
            }
        }
        while(!q.empty())
        {
            int cnt=q.front(); q.pop();
            for(int i=0;i<26;i++)
            {
                if(trie[cnt][i])
                {
                    next[trie[cnt][i]]=trie[next[cnt]][i];
                    q.push(trie[cnt][i]);
                }
                else
                trie[cnt][i]=trie[next[cnt]][i];
            }
        }
    }
    int find(string s)
    {
        int size=s.size();
        int now=0,ans=0;
        for(int i=0;i<size;i++)
        {
            int x=s[i]-'a';
            now=trie[now][x];
            for(int k=now;k&&-(_word[k]+1);k=next[k])//“k&&_word[k]”走到头再找next ,不卡时间过不去 
            {
                ans+=_word[k];
                _word[k]=-1;
            }
        }
        return ans;
    }
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            string s;
            cin>>s;
            _insert(s);
        }
        get_next();
        string s;
        cin>>s;
        cout<<find(s);
    }

    进阶版

    不同的是,他让你求哪个串出现的次数最多,而且可能有许多串出现的次数一样多。

    因为输入的特殊性,我们在输入时赋予每个串的结尾一个映射belong[i],记录这个串是第几个输入进去的(显然纵然有重复模式串也不影响答案)

    然后对于每次查找,我们另数组_time[k]表示k这个字符串出现了几次,在查找时每找到一个点k,我们令_time[belong[k]]++.

    最后顺序统计答案。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 #include<map>
     5 #include<string>
     6 #include<queue>
     7 #include<cstring>
     8 using namespace std;
     9 int trie[100010][30],next[100010],n,t,total,_time[100010],belong[100010];
    10 queue<int>q;
    11 string s[100010];
    12 void _insert(string s1,int j)
    13 {
    14     int size=s1.size(),now=0;
    15     for(int i=0;i<size;i++)
    16     {
    17         int x=s1[i]-'a';
    18         if(!trie[now][x])
    19         trie[now][x]=++total;
    20         now=trie[now][x];
    21     }
    22     belong[now]=j;
    23 }
    24 void get_next()
    25 {
    26     for(int i=0;i<26;i++)
    27     if(trie[0][i])
    28     q.push(trie[0][i]);
    29     while(!q.empty())
    30     {
    31         int cnt=q.front(); q.pop();
    32         for(int i=0;i<26;i++)
    33         {
    34             if(trie[cnt][i])
    35             {
    36                 next[trie[cnt][i]]=trie[next[cnt]][i];
    37                 q.push(trie[cnt][i]);
    38             }
    39             else
    40             trie[cnt][i]=trie[next[cnt]][i];
    41         }
    42     }
    43 }
    44 int find(string s1)
    45 {
    46     int size=s1.size();
    47     int now=0,ans=0;
    48     for(int i=0;i<size;i++)
    49     {
    50         int x=s1[i]-'a';
    51         now=trie[now][x];
    52         for(int k=now;k;k=next[k])
    53         _time[belong[k]]++;            
    54     }
    55     for(int i=1;i<=n;i++)
    56     ans=max(ans,_time[i]);
    57     return ans;
    58 }
    59 int main()
    60 {
    61     while(scanf("%d",&n)!=EOF)
    62     {
    63         if(n==0)break;
    64         memset(next,0,sizeof(next));
    65         memset(_time,0,sizeof(_time));
    66         memset(trie,0,sizeof(trie));
    67         memset(belong,0,sizeof(belong));
    68         total=0;
    69         for(int i=1;i<=n;i++)
    70         {
    71             cin>>s[i];
    72             _insert(s[i],i);    
    73         }
    74         get_next();
    75         string s1;
    76         cin>>s1;
    77         int num=find(s1);
    78         printf("%d
    ",num);
    79         for(int i=1;i<=n;i++)
    80         {
    81             if(_time[i]==num)
    82             cout<<s[i]<<endl;
    83         }
    84     }    
    85 }

    AC自动机是一种优秀的多模式串匹配算法,应用广泛,希望大家能深入理解。

  • 相关阅读:
    查询数据库表的列字段、字段类型、字段长度、是否为空
    AndroidManifest.xml配置文件详解(转载)
    SQL Server Profiler(转载)
    sql 临时表
    sql 全局查询
    react.js
    middleware
    el
    jade模板使用心得
    jade template
  • 原文地址:https://www.cnblogs.com/zzh666/p/9011085.html
Copyright © 2020-2023  润新知