模板 - AC自動機 (滿註釋模板)

ACM-ICPC模板


AC自動機是一種多模匹配算法
AC自動機的關鍵是:構建字典圖實現自動跳轉,構建失配指針實現多模式匹配。


給定n個模式串和1個文本串,求有多少個模式串在文本串裏出現過。

玩命寫註釋

/*luogu P3808 【模板】AC自動機(簡單版)*/

struct Trie{//trie樹
    int fail;//失配指針
    int vis[30];//子結點的位置編號
    //vis數組實際上就是trie樹裏的trie[p][ch],指向的是該結點的子結點的編號
    int End;//trie樹裏用於標記有幾個單詞是以這個結點結尾的
}AC[N];

int tot=0;//AC自動機裏是從根節點0開始,trie樹一般是從根節點1開始

inline void build(string s){//構造tried樹//基本的trie樹的操作
    int len = s.length();
    int p = 0;//trie樹的當前指針(當前結點)從根節點0開始
    for(int i = 0;i < len;++i){
        if(AC[p].vis[s[i]-'a'] == 0)//tire樹的老規矩(不存在這個結點)
            AC[p].vis[s[i]-'a'] = ++tot;//不存在就構造一個新結點
         p = AC[p].vis[s[i]-'a'];//往下走
    }
    AC[p].End++;//標記每一個單詞的結尾//因爲最後要求的是有多少個單詞。要計算重複的
}

void Get_fail(){//構造失配指針利用bfs 遍歷
    queue<int>Q;// 隊列存
    for(int i= 0;i < 26;++i){//處理第二層的失配指針(26個字母遍歷一遍)
        if(AC[0].vis[i] != 0){//若存在這個點
            AC[AC[0].vis[i]].fail = 0;//第二層都要指向根節點
            Q.push(AC[0].vis[i]);//並將該結點編號壓入隊列
        }
    }
    while(!Q.empty()){//遍歷所有的結點
        int u = Q.front();//每次取出隊頭
        Q.pop();
        for(int i = 0;i < 26;++i){//枚舉所有可能的子結點(26個字母)
            if(AC[u].vis[i]!=0){//若存在這個字母的子結點
                AC[AC[u].vis[i]].fail = AC[AC[u].fail].vis[i];//注意是vis[i],一定是指向與該結點字母相同的結點,若不存在就會是指向0(根節點)
                //讓當前結點的子結點的失配指針fail指向,當前結點的失配指針fail所指向的
                //結點的,與當前結點的子結點字母相同的子結點
                Q.push(AC[u].vis[i]);
                //並把該結點壓入隊列裏等待繼續往下遍歷
            }
            else//若不存在子結點
            AC[u].vis[i] = AC[AC[u].fail].vis[i];
            //因爲不存在該結點,本身在trie樹裏會直接結束,但是會浪費時間
            //所以這裏本身沒有路可以走了,
            //但是這裏讓當前結點的子結點直接指向當前結點的失配指針fail指向的結點,
            //把路連上,下次遇上了可以直接走,這樣將trie樹構造成trie圖,更加的優化、
            /////////////////////////////
            //其實就是相當於先預處理好,因爲每一個點都要儘量連到另一個相同字母的結點,每一次遍歷可能儘管這個結點沒有這個字母
            //但是先把他連到結點的失配指針指向的那一個結點,說不定就有這個字母
        }
    }
}

int AC_Query(string s){//AC自動機匹配
    int len = s.length();
    int p = 0,ans = 0;
    for(int i = 0;i < len;++i){
        p = AC[p].vis[s[i]-'a'];//往下一層走
        for(int k = p;k&&AC[k].End!=-1;k = AC[k].fail){//往後走,若沒有到走過的結點或者到達根節點,就一直往失配指針指向的地方走
                ans += AC[k].End;//加上路徑上的值
                AC[k].End = -1;//標記已走過(已經加過一遍了)
        }
    }
    return ans;
}

int n;
string s;

int main()
{
    scanf("%d",&n);
    over(i,1,n){
        cin>>s;
        build(s);//建trie樹(圖)
    }
    AC[0].fail = 0;//結束標誌(根節點的失配指針是一定指向自己的)
    Get_fail();//求出失配指針
    cin>>s;
    cout<<AC_Query(s)<<endl;
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章