AC自动机

AC自动机:

解决多模式串匹配问题

算法流程:

基本流程:
1.建立trie树
2.构造fail指针(指向最长后缀节点,用bfs实现)
3.查询


P3808 AC自动机(简单版)

题意:

给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。

思路:

val(x)数组记录以x结尾的模式串个数,查询的时候累加即可
为了防止fail指针跳回去的时候重复累加,当查询到某个节点之后,把这个节点的val清空,保证只累加一次。

code:

#include<bits/stdc++.h>
using namespace std;
const int maxm=1e6+5;
struct AC{
    int a[maxm][26],fail[maxm],val[maxm],tot=0;
    queue<int>q;
    void init(){//清空
        while(!q.empty())q.pop();
        for(int i=0;i<=tot;i++){
            for(int j=0;j<26;j++){
                a[i][j]=0;
            }
            val[i]=fail[i]=0;
        }
        tot=0;
    }
    void add(char *s){//建立trie树
        int len=strlen(s);
        int node=0;
        for(int i=0;i<len;i++){
            int v=s[i]-'a';
            if(!a[node][v])a[node][v]=++tot;
            node=a[node][v];
        }
        val[node]++;//标记结尾
    }
    void build(){//构造fail
        for(int i=0;i<26;i++){
            if(a[0][i]){
                q.push(a[0][i]);
            }
        }
        while(!q.empty()){
            int x=q.front();
            q.pop();
            for(int i=0;i<26;i++){
                if(a[x][i]){//如果有该字符子节点
                    fail[a[x][i]]=a[fail[x]][i];//(子节点的fail)指向(父节点fail的该字符节点)
                    q.push(a[x][i]);
                }else{//如果没有该字符子节点
                    a[x][i]=a[fail[x]][i];//(该字符子节点)指向(父节点fail的该字符节点)
                }
            }
        }
    }
    int ask(char *s){
        int len=strlen(s);
        int ans=0;
        int node=0;
        for(int i=0;i<len;i++){
            int v=s[i]-'a';
            node=a[node][v];
            for(int t=node;t&&val[t];t=fail[t]){
                ans+=val[t];
                val[t]=0;//匹配过的删掉
            }
        }
        return ans;
    }
}ac;
char s[maxm];
signed main(){
    ac.init();
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",s);
        ac.add(s);
    }
    ac.build();
    scanf("%s",s);
    int ans=ac.ask(s);
    printf("%d\n",ans);
    return 0;
}

P3796 AC自动机(加强版)

题意:

给定n个模式串和一个文本串。
要求输出模式串最多出现的次数,以及次数最多的是哪些模式串。

思路:

val(x)数组记录以x结尾的模式串id,
ans(x)记录第x个串出现的次数
查询的时候记录每个id的串出现的次数,查询完之后遍历ans数组取max即可。

code:

#include<bits/stdc++.h>
using namespace std;
const int maxm=1e6+5;
char s[155][maxm];
char t[maxm];
int ans[maxm];
struct AC{
    int a[maxm][26],fail[maxm],val[maxm],tot=0;
    queue<int>q;
    void init(){//清空
        while(!q.empty())q.pop();
        for(int i=0;i<=tot;i++){
            for(int j=0;j<26;j++){
                a[i][j]=0;
            }
            val[i]=fail[i]=0;
        }
        tot=0;
    }
    void add(char *s,int idx){//建立trie树
        int len=strlen(s);
        int node=0;
        for(int i=0;i<len;i++){
            int v=s[i]-'a';
            if(!a[node][v])a[node][v]=++tot;
            node=a[node][v];
        }
        val[node]=idx;
    }
    void build(){//构造fail
        for(int i=0;i<26;i++){
            if(a[0][i]){
                q.push(a[0][i]);
            }
        }
        while(!q.empty()){
            int x=q.front();
            q.pop();
            for(int i=0;i<26;i++){
                if(a[x][i]){//如果有该字符子节点
                    fail[a[x][i]]=a[fail[x]][i];//(子节点的fail)指向(父节点fail的该字符节点)
                    q.push(a[x][i]);
                }else{//如果没有该字符子节点
                    a[x][i]=a[fail[x]][i];//(该字符子节点)指向(父节点fail的该字符节点)
                }
            }
        }
    }
    void ask(char *s){
        int len=strlen(s);
        int node=0;
        for(int i=0;i<len;i++){
            int v=s[i]-'a';
            node=a[node][v];
            for(int t=node;t;t=fail[t]){
                ans[val[t]]++;
            }
        }
    }
}ac;
signed main(){
    int n;
    while(scanf("%d",&n)!=EOF&&n){
        ac.init();
        for(int i=1;i<=n;i++)ans[i]=0;
        for(int i=1;i<=n;i++){
            scanf("%s",s[i]);
            ac.add(s[i],i);
        }
        ac.build();
        scanf("%s",t);
        ac.ask(t);
        int ma=1;
        for(int i=1;i<=n;i++){
            if(ans[i]>ans[ma]){
                ma=i;
            }
        }
        printf("%d\n",ans[ma]);
        for(int i=1;i<=n;i++){
            if(ans[i]==ans[ma]){
                printf("%s\n",s[i]);
            }
        }
    }
    return 0;
}

P5357 AC自动机(二次加强版)

题意:

给n个模式串和一个文本串,要求输出每个模式串在文本串中出现的次数

思路:

1.和上面一题“P3796 AC自动机(加强版)”似乎差不多,稍微改改就行了。提交发现只有40分。
2.原因是会有重复的串,怎么办呢?
一种方法是在每个节点建立一个链表,这样就能存下多个点的id了。
另一种方法是将每个串的id映射到某个数上idd(id)=x,相同串的idd相同,然后节点存放idd(id)即x,最后ans(idd(id))就是答案。
提交发现只有72分。
3.原因是这题的数据比较强,我们知道每次跳fail指针,层数会变小,但是最少变小一层,如果每次都只减少一层,非常慢。
需要优化一下跳fail的过程:这篇文章里面的拓扑排序优化
优化完就能ac了。

code:

#include<bits/stdc++.h>
using namespace std;
const int maxm=2e6+5;
char s[maxm];
int ans[maxm];
int idd[maxm];//映射
int in[maxm];//入度
struct AC{
    int a[maxm][26],fail[maxm],val[maxm],tot=0;
    int res[maxm];
    queue<int>q;
    void init(){//清空
        while(!q.empty())q.pop();
        for(int i=0;i<=tot;i++){
            for(int j=0;j<26;j++){
                a[i][j]=0;
            }
            val[i]=fail[i]=0;
        }
        tot=0;
    }
    void add(char *s,int idx){//建立trie树
        int len=strlen(s);
        int node=0;
        for(int i=0;i<len;i++){
            int v=s[i]-'a';
            if(!a[node][v])a[node][v]=++tot;
            node=a[node][v];
        }
        if(!val[node])val[node]=idx;
        idd[idx]=val[node];
    }
    void build(){//构造fail
        for(int i=0;i<26;i++){
            if(a[0][i]){
                q.push(a[0][i]);
            }
        }
        while(!q.empty()){
            int x=q.front();
            q.pop();
            for(int i=0;i<26;i++){
                if(a[x][i]){//如果有该字符子节点
                    fail[a[x][i]]=a[fail[x]][i];//(子节点的fail)指向(父节点fail的该字符节点)
                    in[fail[a[x][i]]]++;//入度增加
                    q.push(a[x][i]);
                }else{//如果没有该字符子节点
                    a[x][i]=a[fail[x]][i];//(该字符子节点)指向(父节点fail的该字符节点)
                }
            }
        }
    }
    void ask(char *s){
        int len=strlen(s);
        int node=0;
        for(int i=0;i<len;i++){
            int v=s[i]-'a';
            node=a[node][v];
            res[node]++;
        }
    }
    void topo(){//拓扑
        for(int i=1;i<=tot;i++){
            if(!in[i])q.push(i);
        }
        while(!q.empty()){
            int x=q.front();
            q.pop();
            ans[val[x]]+=res[x];
            int v=fail[x];
            res[v]+=res[x];
            in[v]--;
            if(!in[v])q.push(v);
        }
    }
}ac;
signed main(){
    int n;
    scanf("%d",&n);
    ac.init();
    for(int i=1;i<=n;i++){
        scanf("%s",s);
        ac.add(s,i);
    }
    ac.build();
    scanf("%s",s);
    ac.ask(s);
    ac.topo();
    for(int i=1;i<=n;i++){
        printf("%d\n",ans[idd[i]]);
    }
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章