1. 程式人生 > >AC自動機及其模板

AC自動機及其模板

closed i++ auto ota color namespace 構建 malloc 模板題

模板

技術分享
#include<queue>
#include<stdio.h>
#include<string.h>
using namespace std;

const int Max_Tot = 5e5 + 10;
const int Max_Len = 1e6 + 10;
const int Letter  = 26;

struct Aho{
    struct StateTable{
        int Next[Letter];
        int fail, cnt;
    }Node[Max_Tot];
    int Size;
    queue
<int> que; inline void init(){ while(!que.empty()) que.pop(); memset(Node[0].Next, 0, sizeof(Node[0].Next)); Node[0].fail = Node[0].cnt = 0; Size = 1; } inline void insert(char *s){ int len = strlen(s); int now = 0; for(int i=0; i<len; i++){
int idx = s[i] - a; if(!Node[now].Next[idx]){ memset(Node[Size].Next, 0, sizeof(Node[Size].Next)); Node[Size].fail = Node[Size].cnt = 0; Node[now].Next[idx] = Size++; } now = Node[now].Next[idx]; } Node[now].cnt
++; } inline void BuildFail(){ Node[0].fail = -1; que.push(0); while(!que.empty()){ int top = que.front(); que.pop(); for(int i=0; i<Letter; i++){ if(Node[top].Next[i]){ if(top == 0) Node[ Node[top].Next[i] ].fail = 0; else{ int v = Node[top].fail; while(v != -1){ if(Node[v].Next[i]){ Node[ Node[top].Next[i] ].fail = Node[v].Next[i]; break; }v = Node[v].fail; }if(v == -1) Node[ Node[top].Next[i] ].fail = 0; }que.push(Node[top].Next[i]); } } } } inline void Get(int u, int &res){ while(u){ res += Node[u].cnt; Node[u].cnt = 0; u = Node[u].fail; } } int Match(char *s){ int len = strlen(s); int res = 0, now = 0; for(int i=0; i<len; i++){ int idx = s[i] - a; if(Node[now].Next[idx]) now = Node[now].Next[idx]; else{ int p = Node[now].fail; while(p!=-1 && Node[p].Next[idx]==0) p = Node[p].fail; if(p == -1) now = 0; else now = Node[p].Next[idx]; } if(Node[now].cnt) Get(now, res); } return res; } }ac; char S[Max_Len]; int main(void) { // ac.init(); // ac.BuildFail(); // ac.Match(); // ..... return 0; }
View Code 技術分享
#include<bits/stdc++.h>
using namespace std;

#define MAX_N 1000006  /// 主串長度
#define MAX_Tot 500005 /// 字典樹上可能的最多的結點數 = Max串數 * Max串長

struct Aho{
    struct state{
        int next[26];
        int fail,cnt;
    }st[MAX_Tot]; /// 節點結構體
    int Size; /// 節點個數
    queue<int> que;/// BFS構建fail指針的隊列

    void init(){
        while(que.size())que.pop();/// 清空隊列
        for(int i=0;i<MAX_Tot;i++){/// 初始化節點,有時候 MLE 的時候,可以嘗試將此初始化放到要操作的時候再來初始化
            memset(st[i].next,0,sizeof(st[i].next));
            st[i].fail=st[i].cnt=0;
        }
        Size=1;/// 本來就有一個空的根節點
    }

    void insert(char *S){/// 插入模式串
        int len=strlen(S);/// 復雜度為O(n),所以別寫進for循環
        int now=0;/// 當前結點是哪一個,從0即根開始
        for(int i=0;i<len;i++){
            char c = S[i];
            if(!st[now].next[c-a]) st[now].next[c-a]=Size++;
            now=st[now].next[c-a];
        }
        st[now].cnt++;/// 給這個串末尾打上標記
    }

    void build(){/// 構建 fail 指針
        st[0].fail=-1;/// 根節點的 fail 指向自己
        que.push(0);/// 將根節點入隊

        while(que.size()){
            int top = que.front(); que.pop();

            for(int i=0; i<26; i++){
                if(st[top].next[i]){/// 如果當前節點有 i 這個兒子
                    if(top == 0) st[st[top].next[i]].fail=0;/// 第二層節點 fail 應全指向根
                    else {
                        int v = st[top].fail;/// 走向 top 節點父親的 fail 指針指向的地方,嘗試找一個最長前綴
                        while(v != -1){/// 如果走到 -1 則說明回到根了
                            if(st[v].next[i]){/// 如果有一個最長前綴後面接著的也是 i 這個字符,則說明 top->next[i] 的 fail 指針可以指向這裏
                                st[st[top].next[i]].fail = st[v].next[i];
                                break;/// break 保證找到的前綴是最長的
                            }
                            v = st[v].fail;/// 否則繼續往父親的父親的 fail 跳,即後綴在變短( KMP 思想 )
                        } if(v == -1) st[st[top].next[i]].fail=0;/// 如果從頭到尾都沒找到,那麽就只能指向根了
                    } que.push(st[top].next[i]);/// 將這個節點入隊,為了下面建立 fail 節點做準備
                }
            }
        }
    }

    int get(int u){
        int res = 0;
        while(u){
            res = res + st[u].cnt;
            st[u].cnt = 0;
            u = st[u].fail;
        }
        return res;
    }

    int match(char *S){
        int len = strlen(S);/// 主串長度
        int res=0,now=0;/// 主串能夠和多少個模式串匹配的結果、當前的節點是哪一個
        for(int i=0; i<len; i++){
            char c = S[i];
            if(st[now].next[c-a]) now=st[now].next[c-a];/// 如果匹配了,則不用跳到 fail 處,直接往下一個字符匹配
            else {
                int p = st[now].fail;
                while( p!=-1 && st[p].next[c-a]==0 ) p=st[p].fail;/// 跳到 fail 指針處去匹配 c-‘a‘ ,直到跳到 -1 也就是沒得跳的時候
                if(p == -1) now = 0;/// 如果全部都不匹配,只能回到根節點了
                else now = st[p].next[c-a];/// 否則當前節點就是到達了能夠匹配的 fail 指針指向處
            }
            if(st[now].cnt)/// 如果當前節點是個字符串的結尾,這個時候就能統計其對於答案貢獻了,答案的貢獻應該是它自己 + 它所有 fail 指針指向的節點
                           /// 實際也就是它匹配了,那麽它的 fail 指向的前綴以及 fail 的 fail 實際上也應該是匹配了,所以循環跳 fail 直到無法再跳為止
                res = res + get(now);
        }
        return res;
    }
}ac;

int T;
int N;
char S[MAX_N];
int main(){
    // ac.init();
    // ac.build();
    // ac.match();
    // ...
    return 0;
}
帶註釋 技術分享
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
struct Node  
{  
    int cnt;//是否為該單詞的最後一個結點   
    Node *fail;//失敗指針   
    Node *next[26];//Trie中每個結點的各個節點   
}*queue[500005];//隊列,方便用BFS構造失敗指針   
char s[1000005];//主字符串   
char keyword[55];//需要查找的單詞   
Node *root;//頭結點   
void Init(Node *root)//每個結點的初始化   
{  
    root->cnt=0;  
    root->fail=NULL;  
    for(int i=0;i<26;i++)  
        root->next[i]=NULL;  
}  
void Build_trie(char *keyword)//構建Trie樹   
{  
    Node *p,*q;  
    int i,v;  
    int len=strlen(keyword);  
    for(i=0,p=root;i<len;i++)  
    {  
        v=keyword[i]-a;  
        if(p->next[v]==NULL)  
        {  
            q=(struct Node *)malloc(sizeof(Node));  
            Init(q);  
            p->next[v]=q;//結點鏈接   
        }  
        p=p->next[v];//指針移動到下一個結點   
    }  
    p->cnt++;//單詞最後一個結點cnt++,代表一個單詞   
}  
void Build_AC_automation(Node *root)  
{  
    int head=0,tail=0;//隊列頭、尾指針   
    queue[head++]=root;//先將root入隊   
    while(head!=tail)  
    {  
        Node *p=NULL;  
        Node *temp=queue[tail++];//彈出隊頭結點   
        for(int i=0;i<26;i++)  
        {  
            if(temp->next[i]!=NULL)//找到實際存在的字符結點   
            { //temp->next[i] 為該結點,temp為其父結點   
                if(temp==root)//若是第一層中的字符結點,則把該結點的失敗指針指向root   
                    temp->next[i]->fail=root;  
                else  
                {  
                    //依次回溯該節點的父節點的失敗指針直到某節點的next[i]與該節點相同,  
                    //則把該節點的失敗指針指向該next[i]節點;   
                    //若回溯到 root 都沒有找到,則該節點的失敗指針指向 root  
                    p=temp->fail;//將該結點的父結點的失敗指針給p   
                    while(p!=NULL)  
                    {  
                        if(p->next[i]!=NULL)  
                        {  
                            temp->next[i]->fail=p->next[i];  
                            break;  
                        }  
                        p=p->fail;  
                    }  
                    //讓該結點的失敗指針也指向root   
                    if(p==NULL)  
                        temp->next[i]->fail=root;  
                }  
                queue[head++]=temp->next[i];//每處理一個結點,都讓該結點的所有孩子依次入隊   
            }  
        }  
    }  
}  
int query(Node *root)  
{ //i為主串指針,p為模式串指針   
    int i,v,count=0;  
    Node *p=root;  
    int len=strlen(s);  
    for(i=0;i<len;i++)  
    {  
        v=s[i]-a;  
        //由失敗指針回溯查找,判斷s[i]是否存在於Trie樹中   
        while(p->next[v]==NULL && p!=root)  
            p=p->fail;  
        p=p->next[v];//找到後p指針指向該結點   
        if(p==NULL)//若指針返回為空,則沒有找到與之匹配的字符   
            p=root;  
        Node *temp=p;//匹配該結點後,沿其失敗指針回溯,判斷其它結點是否匹配   
        while(temp!=root)//匹配結束控制   
        {  
            if(temp->cnt>=0)//判斷該結點是否被訪問   
            {  
                count+=temp->cnt;//由於cnt初始化為 0,所以只有cnt>0時才統計了單詞的個數   
                temp->cnt=-1;//標記已訪問過   
            }  
            else//結點已訪問,退出循環   
                break;  
            temp=temp->fail;//回溯 失敗指針 繼續尋找下一個滿足條件的結點   
        }  
    }  
    return count;  
}  
int main()  
{  
    int T,n;  
    scanf("%d",&T);  
    while(T--)  
    {  
        root=(struct Node *)malloc(sizeof(Node));  
        Init(root);  
        scanf("%d",&n);  
        for(int i=0;i<n;i++)  
        {  
            scanf("\n%s",keyword);  
            Build_trie(keyword);  
        }  
        Build_AC_automation(root);  
        scanf("\n%s",s);  
        printf("%d\n",query(root));  
    }  
    return 0;  
}  
指針版

參考博客

http://blog.csdn.net/niushuai666/article/details/7002823
http://blog.csdn.net/silence401/article/details/52662605
http://blog.csdn.net/liu940204/article/details/51345954
http://blog.csdn.net/creatorx/article/details/71100840

相關題目

HDU 2222

題意 : 給出 n 個模式串再給出一個主串,問你有多少個模式串曾在這個主串上出現過

分析 : 模板題,註意每一次計數完成後要將 cnt 的值置為 0 以免重復計算

技術分享
#include<bits/stdc++.h>
using namespace std;

#define MAX_N 1000006  /// 主串長度
#define MAX_Tot 500005 /// 字典樹上可能的最多的結點數 = Max串數 * Max串長

struct Aho{
    struct state{
        int next[26];
        int fail,cnt;
    }st[MAX_Tot]; /// 節點結構體
    int Size; /// 節點個數
    queue<int> que;/// BFS構建fail指針的隊列

    void init(){
        while(que.size())que.pop();/// 清空隊列
        for(int i=0;i<MAX_Tot;i++){/// 初始化節點,有時候 MLE 的時候,可以嘗試將此初始化放到要操作的時候再來初始化
            memset(st[i].next,0,sizeof(st[i].next));
            st[i].fail=st[i].cnt=0;
        }
        Size=1;/// 本來就有一個空的根節點
    }

    void insert(char *S){/// 插入模式串
        int len=strlen(S);/// 復雜度為O(n),所以別寫進for循環
        int now=0;/// 當前結點是哪一個,從0即根開始
        for(int i=0;i<len;i++){
            char c = S[i];
            if(!st[now].next[c-a]) st[now].next[c-a]=Size++;
            now=st[now].next[c-a];
        }
        st[now].cnt++;/// 給這個串末尾打上標記
    }

    void build(){/// 構建 fail 指針
        st[0].fail=-1;/// 根節點的 fail 指向自己
        que.push(0);/// 將根節點入隊

        while(que.size()){
            int top = que.front(); que.pop();

            for(int i=0; i<26; i++){
                if(st[top].next[i]){/// 如果當前節點有 i 這個兒子
                    if(top == 0) st[st[top].next[i]].fail=0;/// 第二層節點 fail 應全指向根
                    else {
                        int v = st[top].fail;/// 走向 top 節點父親的 fail 指針指向的地方,嘗試找一個最長前綴
                        while(v != -1){/// 如果走到 -1 則說明回到根了
                            if(st[v].next[i]){/// 如果有一個最長前綴後面接著的也是 i 這個字符,則說明 top->next[i] 的 fail 指針可以指向這裏
                                st[st[top].next[i]].fail = st[v].next[i];
                                break;/// break 保證找到的前綴是最長的
                            }
                            v = st[v].fail;/// 否則繼續往父親的父親的 fail 跳,即後綴在變短( KMP 思想 )
                        } if(v == -1) st[st[top].next[i]].fail=0;/// 如果從頭到尾都沒找到,那麽就只能指向根了
                    } que.push(st[top].next[i]);/// 將這個節點入隊,為了下面建立 fail 節點做準備
                }
            }
        }
    }

    int get(int u){
        int res = 0;
        while(u){
            res = res + st[u].cnt;
            st[u].cnt = 0;
            u = st[u].fail;
        }
        return res;
    }

    int match(char *S){
        int len = strlen(S);/// 主串長度
        int res=0,now=0;/// 主串能夠和多少個模式串匹配的結果、當前的節點是哪一個
        for(int i=0; i<len; i++){
            char c = S[i];
            if(st[now].next[c-a]) now=st[now].next[c-a];/// 如果匹配了,則不用跳到 fail 處,直接往下一個字符匹配
            else {
                int p = st[now].fail;
                while( p!=-1 && st[p].next[c-a]==0 ) p=st[p].fail;/// 跳到 fail 指針處去匹配 c-‘a‘ ,直到跳到 -1 也就是沒得跳的時候
                if(p == -1) now = 0;/// 如果全部都不匹配,只能回到根節點了
                else now = st[p].next[c-a];/// 否則當前節點就是到達了能夠匹配的 fail 指針指向處
            }
            if(st[now].cnt)/// 如果當前節點是個字符串的結尾,這個時候就能統計其對於答案貢獻了,答案的貢獻應該是它自己 + 它所有 fail 指針指向的節點
                           /// 實際也就是它匹配了,那麽它的 fail 指向的前綴以及 fail 的 fail 實際上也應該是匹配了,所以循環跳 fail 直到無法再跳為止
                res = res + get(now);
        }
        return res;
    }
}aho;

int T;
int N;
char S[MAX_N];
int main(){
    scanf("%d",&T);
    while(T--){
        aho.init();
        scanf("%d",&N);
        for(int i=0;i<N;i++){
            scanf("%s",S);
            aho.insert(S);
        }
        aho.build();
        scanf("%s",S);
        printf("%d\n",aho.match(S));
    }
    return 0;
}
View Code

HDU 2896

題意 : 中文就不贅述了……

分析 : 模板題,可見的ascii碼範圍的話,直接開到128即可

技術分享
#include<queue>
#include<stdio.h>
#include<string.h>
using namespace std;

const int Max_Tot = 1e5 + 10;
const int Max_Len = 1e4 + 10;
const int Letter  = 128;

struct Aho{
    struct StateTable{
        int Next[Letter];
        int fail, id;
    }Node[Max_Tot];
    int Size;
    queue<int> que;

    inline void init(){
        while(!que.empty()) que.pop();
        memset(Node[0].Next, 0, sizeof(Node[0].Next));
        Node[0].fail = Node[0].id = 0;
        Size = 1;
    }

    inline void insert(char *s, const int id){
        int len = strlen(s);
        int now = 0;
        for(int i=0; i<len; i++){
            int idx = s[i];
            if(!Node[now].Next[idx]){
                memset(Node[Size].Next, 0, sizeof(Node[Size].Next));
                Node[Size].fail = Node[Size].id = 0;
                Node[now].Next[idx] = Size++;
            }
            now = Node[now].Next[idx];
        }
        Node[now].id = id;
    }

    inline void BuildFail(){
        Node[0].fail = -1;
        que.push(0);
        while(!que.empty()){
            int top = que.front();  que.pop();
            for(int i=0; i<Letter; i++){
                if(Node[top].Next[i]){
                    if(top == 0) Node[ Node[top].Next[i] ].fail = 0;
                    else{
                        int v = Node[top].fail;
                        while(v != -1){
                            if(Node[v].Next[i]){
                                Node[ Node[top].Next[i] ].fail = Node[v].Next[i];
                                break;
                            }v = Node[v].fail;
                        }if(v == -1) Node[ Node[top].Next[i] ].fail = 0;
                    }que.push(Node[top].Next[i]);
                }
            }
        }
    }

    inline void Get(int u, bool *used){
        while(u){
            if(!used[Node[u].id] && Node[u].id)
                used[Node[u].id] = true;
            u = Node[u].fail;
        }
    }

    bool Match(char *s, bool *used){
        int now = 0;
        bool ok = false;
        for(int i=0; s[i]; i++){
            int idx = s[i];
            if(Node[now].Next[idx]) now = Node[now].Next[idx];
            else{
                int p = Node[now].fail;
                while(p!=-1 && Node[p].Next[idx]==0){
                    p = Node[p].fail;
                }
                if(p == -1) now = 0;
                else now = Node[p].Next[idx];
            }
            if(Node[now].id) { Get(now, used); ok = true; }
        }
        if(ok) return true;
        return false;
    }
}ac;

char S[Max_Len];
bool used[501];
int main(void)
{
    int n, m;
    memset(used, false, sizeof(used));
    while(~scanf("%d", &n)){
        ac.init();
        for(int i=1; i<=n; i++){
            scanf("%s", S);
            ac.insert(S, i);
        }
        ac.BuildFail();
        int ans = 0;
        scanf("%d", &m);
        for(int i=1; i<=m; i++){
            scanf("%s", S);
            if(ac.Match(S, used)){
                printf("web %d:", i);
                for(int j=1; j<=n; j++){
                    if(used[j]){
                        printf(" %d", j);
                        used[j] = false;
                    }
                }puts("");
                ans++;
            }
        }
        printf("total: %d\n", ans);
    }
    return 0;
}
View Code

AC自動機及其模板