AC自动机复习

AC 自动机

应用:

  • 多模式串匹配

构建

  • 将所有模式串构成一个 Trie 树
  • 树上的每个节点代表某一个(或多个)模式串的前缀
  • 每个节点同时有一个失配指针fail,指向当前节点所代表的的串可以匹配到的最长前缀,这个前缀还必须是这个串的后缀。类似于KMPnext数组,但KMPnext数组是对于一个串而言,在 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 @   Arashimu  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示