AC 自动机

AC 自动机

AC 自动机是 以 Trie 的结构为基础,结合 KMP 的思想 建立的。

简要步骤

  1. 将所有的模式串构成一棵 \(Trie\)
  2. \(Trie\) 树上所有的结点构造失配指针。

然后就可以利用它进行多模式串匹配了。

\(Trie\)

il void ins(char s[]){
    ri int now=0,len=strlen(s); // 一定要记录len,不然会T 
    for(ri int i=0,o;o=s[i]-'a',i<len;++i){
        if(!t[now].nxt[o]) t[now].nxt[o]=++id;
        now=t[now].nxt[o];
    }
    return operate(); // 一些操作,如t[now].cnt++
}

构造失配指针

il void getfail(){
    ri int now=0;
    for(ri int i=0;i<26;++i){
        if(t[now].nxt[i]) q.push(t[now].nxt[i]);
    }
    while(!q.empty()){
        now=q.front(),q.pop();
        for(ri int i=0;i<26;++i){
            if(t[now].nxt[i]){
                t[t[now].nxt[i]].fail=t[t[now].fail].nxt[i];
                q.push(t[now].nxt[i]);
            }
            else t[now].nxt[i]=t[t[now].fail].nxt[i];
        }
    }
    return;
}

求有多少个不同的模式串在文本串里出现过

il void ins1(char s[]){
    ri int now=0,len=strlen(s);
    for(ri int i=0,o;o=s[i]-'a',i<len;++i){
        if(!t[now].nxt[o]) t[now].nxt[o]=++id;
        now=t[now].nxt[o];
    }
    return t[now].cnt++,void();
}
il int ask1(char s[]){ 
// 求有多少个不同的模式串在文本串里出现过
    ri int as=0,now=0,len=strlen(s);
    for(ri int i=0,o;o=s[i]-'a',i<len;++i){
        now=t[now].nxt[o];
        for(ri int v=now;v&&~t[v].cnt;v=t[v].fail){
            as+=t[v].cnt,t[v].cnt=-1;
        }
    }
    return as;
}   
code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;

cs int N=1e6+1;

namespace AC_Automaton{
    struct trie{
        int fail,cnt,nxt[26];
    }t[N];
    int id;queue<int> q;
    il void ins1(char s[]){
        ri int now=0,len=strlen(s);
        for(ri int i=0,o;o=s[i]-'a',i<len;++i){
            if(!t[now].nxt[o]) t[now].nxt[o]=++id;
            now=t[now].nxt[o];
        }
        return t[now].cnt++,void();
    }
    il void getfail(){
        ri int now=0;
        for(ri int i=0;i<26;++i){
            if(t[now].nxt[i]) q.push(t[now].nxt[i]);
        }
        while(!q.empty()){
            now=q.front(),q.pop();
            for(ri int i=0;i<26;++i){
                if(t[now].nxt[i]){
                    t[t[now].nxt[i]].fail=t[t[now].fail].nxt[i];
                    q.push(t[now].nxt[i]);
                }
                else t[now].nxt[i]=t[t[now].fail].nxt[i];
            }
        }
        return;
    }
    il int ask1(char s[]){ 
    // 求有多少个不同的模式串在文本串里出现过
        ri int as=0,now=0,len=strlen(s);
        for(ri int i=0,o;o=s[i]-'a',i<len;++i){
            now=t[now].nxt[o];
            for(ri int v=now;v&&~t[v].cnt;v=t[v].fail){
                as+=t[v].cnt,t[v].cnt=-1;
            }
        }
        return as;
    }
} using namespace AC_Automaton;

signed main(){
    ri int n;char s[N];scanf("%d",&n);
    while(n--) scanf("%s",s),ins1(s);
    getfail(),scanf("%s",s),printf("%d",ask1(s));
    return 0;
}

求模式串在文本串中出现次数(小数据)

il void ins2(char s[],int tot){
    ri int now=0,len=strlen(s); 
    for(ri int i=0,o;o=s[i]-'a',i<len;++i){
        if(!t[now].nxt[o]) t[now].nxt[o]=++id;
        now=t[now].nxt[o];
    }
    return t[now].cnt=tot,void();        
} 
il void ask2(char s[],int n){
// 求出现次数
    for(ri int i=2;i<=n;++i) as[i]=Id[i]=0;
    tot=as[1]=0,Id[0]=1;
    ri int now=0,len=strlen(s);
    for(ri int i=0,o;o=s[i]-'a',i<len;++i){
        now=t[now].nxt[o];
        for(ri int v=now;v;v=t[v].fail){
            as[t[v].cnt]++;
        }
    }
    return;
}
code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;

cs int N=1e6+1,T=151,L=71;

namespace AC_Automaton{
    struct trie{
        int fail,cnt,nxt[26];
    }t[N];
    int id;queue<int> q;
    il void ins2(char s[],int tot){
        ri int now=0,len=strlen(s); 
        for(ri int i=0,o;o=s[i]-'a',i<len;++i){
            if(!t[now].nxt[o]) t[now].nxt[o]=++id;
            now=t[now].nxt[o];
        }
        return t[now].cnt=tot,void();        
    } 
    il void getfail(){
        ri int now=0;
        for(ri int i=0;i<26;++i){
            if(t[now].nxt[i]) q.push(t[now].nxt[i]);
        }
        while(!q.empty()){
            now=q.front(),q.pop();
            for(ri int i=0;i<26;++i){
                if(t[now].nxt[i]){
                    t[t[now].nxt[i]].fail=t[t[now].fail].nxt[i];
                    q.push(t[now].nxt[i]);
                }
                else t[now].nxt[i]=t[t[now].fail].nxt[i];
            }
        }
        return;
    }
    il void clr(){
        for(ri int i=0;i<=id;++i){
            memset(t[i].nxt,0,sizeof(int)*26);
            t[i].fail=t[i].cnt=0;
        }
        return id=0,void();
    }
    int as[T],Id[T],tot;
    il void ask2(char s[],int n){
    // 求哪些模式串在文本串中出现的次数最多
        for(ri int i=2;i<=n;++i) as[i]=Id[i]=0;
        tot=as[1]=0,Id[0]=1;
        ri int now=0,len=strlen(s);
        for(ri int i=0,o;o=s[i]-'a',i<len;++i){
            now=t[now].nxt[o];
            for(ri int v=now;v;v=t[v].fail){
                as[t[v].cnt]++;
            }
        }
        for(ri int i=2;i<=n;++i){
            if(as[i]==as[Id[0]]) Id[++tot]=i;
            else if(as[i]>as[Id[0]]) Id[tot=0]=i;
        }
        return;
    }
} using namespace AC_Automaton;

signed main(){
    ri int n;char s[T][L],c[N];scanf("%d",&n);
    while(n){
        for(ri int i=1;i<=n;++i){
            scanf("%s",s[i]),ins2(s[i],i);
        }
        getfail(),scanf("%s",c),ask2(c,n);
        printf("%d\n",as[Id[0]]);
        for(ri int i=0;i<=tot;++i){
            printf("%s\n",s[Id[i]]);
        }
        clr(),scanf("%d",&n);
    } 
    return 0;
}

求模式串在文本串中出现次数(大数据)

il void ins3(char s[],int tot){
    ri int now=0,len=strlen(s); 
    for(ri int i=0,o;o=s[i]-'a',i<len;++i){
        if(!t[now].nxt[o]) t[now].nxt[o]=++id;
        now=t[now].nxt[o];
    }
    return Id[tot]=now,void(); 
// 记录每个模式串在 Trie 树上的终止节点
} 
il void ask3(char s[],int n){
// 求模式串在文本串中出现的次数
    ri int now=0,len=strlen(s);
    for(ri int i=0,o;o=s[i]-'a',i<len;++i){
        now=t[now].nxt[o];
        as[now]++;
    }
    return;
}

优化

暴力跳 \(fail\) 复杂度最劣 \(O(n m)\),效率太低
考虑建出 \(fail\) 树,记录自动机上的每个状态被匹配了几次,最后求出每个模式串在 \(Trie\) 上的终止节点在 \(fail\) 树上的子树总匹配次数,复杂度 \(O(n + m)\)

namespace qwq{ //优化
    int h[N];
    struct edge{int v,nxt; }e[N];
    il void add(int u,int v,int i){
        e[i]={v,h[u]},h[u]=i;
    }
    il void rbuild(){
        for(ri int i=1;i<=id;++i){
            add(t[i].fail,i,i);
        } 
    }
    il void dfs(int u){
        for(ri int i=h[u];i;i=e[i].nxt){
            dfs(e[i].v),as[u]+=as[e[i].v];
        }
        return;
    }
} using namespace qwq;
code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;

cs int N=2e6+1,T=2e5+1;

namespace AC_Automaton{
    struct trie{
        int fail,nxt[26];
    }t[N];
    int id,Id[T];queue<int> q;
    il void ins3(char s[],int tot){
        ri int now=0,len=strlen(s); 
        for(ri int i=0,o;o=s[i]-'a',i<len;++i){
            if(!t[now].nxt[o]) t[now].nxt[o]=++id;
            now=t[now].nxt[o];
        }
        return Id[tot]=now,void(); 
    } 
    il void getfail(){
        ri int now=0;
        for(ri int i=0;i<26;++i){
            if(t[now].nxt[i]) q.push(t[now].nxt[i]);
        }
        while(!q.empty()){
            now=q.front(),q.pop();
            for(ri int i=0;i<26;++i){
                if(t[now].nxt[i]){
                    t[t[now].nxt[i]].fail=t[t[now].fail].nxt[i];
                    q.push(t[now].nxt[i]);
                }
                else t[now].nxt[i]=t[t[now].fail].nxt[i];
            }
        }
        return;
    }
    int as[T];
    il void ask3(char s[],int n){
    // 求模式串在文本串中出现的次数
        ri int now=0,len=strlen(s);
        for(ri int i=0,o;o=s[i]-'a',i<len;++i){
            now=t[now].nxt[o];
            as[now]++;
        }
        return;
    }
} using namespace AC_Automaton;

namespace qwq{ //优化
    int h[N];
    struct edge{int v,nxt; }e[N];
    il void add(int u,int v,int i){
        e[i]={v,h[u]},h[u]=i;
    }
    il void rbuild(){
        for(ri int i=1;i<=id;++i){
            add(t[i].fail,i,i);
        } 
    }
    il void dfs(int u){
        for(ri int i=h[u];i;i=e[i].nxt){
            dfs(e[i].v),as[u]+=as[e[i].v];
        }
        return;
    }
} using namespace qwq;

signed main(){
    ri int n;char s[N];scanf("%d",&n);
    for(ri int i=1;i<=n;++i){
        scanf("%s",s),ins3(s,i);
    } 
    getfail(),scanf("%s",s),ask3(s,n);
    rbuild(),dfs(0);
    for(ri int i=1;i<=n;++i){
        printf("%d\n",as[Id[i]]);
    }
    return 0;
}

edit

posted @ 2023-01-31 11:30  雨夜风月  阅读(17)  评论(0编辑  收藏  举报