AC自动机复习

AC 自动机

应用:

  • 多模式串匹配

构建

  • 将所有模式串构成一个 Trie 树
  • 树上的每个节点代表某一个(或多个)模式串的前缀
  • 每个节点同时有一个失配指针\(fail\),指向当前节点所代表的的串可以匹配到的最长前缀,这个前缀还必须是这个串的后缀。类似于\(KMP\)\(next\)数组,但\(KMP\)\(next\)数组是对于一个串而言,在 AC 自动机里一个节点的\(fail\)指针不一定指向自己的前缀,可以是其他模式串的前缀。
  • 寻找\(fail\)指针,可以假设比这个极点高度低的极点的\(fail\)都寻找完了,所以我们是通过\(BFS\)的方式去寻找\(fail\)指针
  • 根据题目的不同,每个节点额外记录的信息也不同,比如每个节点可以表示的模式串个数\(cnt\),每个节点表示的模式串的编号\(id\)等等

模板

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;

namespace AC
{
   int tr[N][26],tot;
   int fail[N],cnt[N]; //cnt[i]表示第i个节点可以表示的模式串个数

   void insert(char *s){  //构建Trie树
     int u=0;
     for(int i=0;s[i];i++){
          int x=s[i]-'a';
          if(!tr[u][x]) tr[u][x]=++tot;
          u=tr[u][x];
     }
     cnt[u]++;
   }

   void build(){
     queue<int>q;
     for(int i=0;i<26;i++)  //先把所有0号节点可以到达的点入队
        if(tr[0][i]) q.push(tr[0][i]);

     while(q.size()){
        int u=q.front();
        q.pop();
        for(int i=0;i<26;i++){
            int &x=tr[u][i];
            if(x){
                fail[x]=tr[fail[u]][i];
                q.push(x);
            }else{
                x=tr[fail[u]][i];
            }
        }
     }
   }
}
char s[N],t[N];
int main()
{
    //ios::sync_with_stdio(false);
    //cin.tie(nullptr);
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++) {
        scanf("%s",s);
        AC::insert(s);
    }
    AC::build();
}

image

应用

1.多模式串匹配 1

Problem

多模式串匹配:给定一个文本串\(t\)和多个模式串\(s\),问有多少个模式串在文本串中出现过

Solve

每个节点记录表示的模式串个数,然后在 Trie 树上沿着文本串走,当走到一个节点假设这个节点是从\(x\)转移过来的,那么沿着这个节点跳\(fail\),因为若存在其他模式串\(s\)并且是以\(x\)结尾的,那么在当前文本串走过的路径形成的字符串\(t^{'}\)中,\(s\)若在\(t^{'}\)中出现,则一定是\(t^{'}\)的一个后缀,然后我们对每个跳过的节点标记,这样对于相同的\(x\)就不会重复了。

Code

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
namespace AC
{
   int tr[N][26],tot;
   int fail[N],cnt[N]; //cnt[i]表示第i个节点可以表示的模式串个数

   void insert(char *s){  //构建Trie树
     int u=0;
     for(int i=0;s[i];i++){
          int x=s[i]-'a';
          if(!tr[u][x]) tr[u][x]=++tot;
          u=tr[u][x];
     }
     cnt[u]++;
   }
   void build(){
     queue<int>q;
     for(int i=0;i<26;i++)  //先把所有0号节点可以到达的点入队
        if(tr[0][i]) q.push(tr[0][i]);

     while(q.size()){
        int u=q.front();
        q.pop();
        for(int i=0;i<26;i++){
            int &x=tr[u][i];
            if(x){
                fail[x]=tr[fail[u]][i];
                q.push(x);
            }else{
                x=tr[fail[u]][i];
            }
        }
     }
   }
   int query(char *t){
     int u=0,res=0;
     for(int i=0;t[i];i++){
          u=tr[u][t[i]-'a'];
          for(int j=u;j&&cnt[j]!=-1;j=fail[j]){
             res+=cnt[j],cnt[j]=-1;
          }
     }
     return res;
   }
}
char s[N],t[N];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++) {
        scanf("%s",s);
        AC::insert(s);
    }
    AC::build();
    scanf("%s",t);
    printf("%d\n",AC::query(t));
}

2.多模式串匹配 2

Problem

给定多个模式串\(s\)和一个文本串\(t\),问哪些模式串在文本串中出现的次数最多,并输出这些模式串

Solve

由于不会有两个模式串相同,所以可以给每个节点记录一个可以表示的模式串的编号。然后沿着\(t\)在 Trie 上走,每次到达一个节点就跳\(fail\),并且记录每个节点是通过跳\(fail\)到达的次数,最后查询哪些可以表示字符串的节点可以被跳到的最多次数即可。

Code

#include <bits/stdc++.h>
using namespace std;
const int N=155;

namespace AC
{
    const int NN=N*80;
   int tr[NN][26],tot;
   int fail[NN],cnt[NN],id[NN]; //cnt[i]表示第i个节点可以表示的模式串个数
   int val[NN];

   void init(){
      tot=0;
      memset(tr,0,sizeof tr);
      memset(fail,0,sizeof fail);
      memset(cnt,0,sizeof cnt);
      memset(id,0,sizeof id);
      memset(val,0,sizeof val);
   }

   void insert(char *s,int idx){  //构建Trie树
     int u=0;
     for(int i=0;s[i];i++){
          int x=s[i]-'a';
          if(!tr[u][x]) tr[u][x]=++tot;
          u=tr[u][x];
     }
     id[u]=idx;
     cnt[u]++;
   }

   void build(){
     queue<int>q;
     for(int i=0;i<26;i++)  //先把所有0号节点可以到达的点入队
        if(tr[0][i]) q.push(tr[0][i]);

     while(q.size()){
        int u=q.front();
        q.pop();
        for(int i=0;i<26;i++){
            int &x=tr[u][i];
            if(x){
                fail[x]=tr[fail[u]][i];
                q.push(x);
            }else{
                x=tr[fail[u]][i];
            }
        }
     }
   }
   int query(char *t){
     int u=0,res=0;
     for(int i=0;t[i];i++){
          u=tr[u][t[i]-'a'];
          for(int j=u;j;j=fail[j]) val[j]++;
     }
     for(int i=0;i<=tot;i++)
         if(id[i]) res=max(res,val[i]),cnt[id[i]]=val[i];
     return res;
   }
}
char s[151][76];
char t[1000005];
int main()
{
    int n;
    while(scanf("%d",&n),n){
       AC::init();
       for(int i=1;i<=n;i++){
         scanf("%s",s[i]);
         AC::insert(s[i],i);
       }
       AC::build();
       scanf("%s",t);
       int res=AC::query(t);
       printf("%d\n",res);
       for(int i=1;i<=n;i++)
            if(AC::cnt[i]==res) printf("%s\n",s[i]);
    }
}
posted @ 2022-08-06 11:08  Arashimu  阅读(10)  评论(0编辑  收藏  举报