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;
}

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