【算法筆記】Aho-Corasick 算法(AC自動機) 小結

參考:

《算法競賽入門經典-訓練指南》
http://codeforces.com/blog/entry/14854
kuang bin blog

加上不存在的邊,並壓縮冗餘轉移

這個改進《訓練指南》中說過, 本文的目的是總結並實現一個自用的模板。。
AC自動機每個節點有一個 fail 指針, 作用與KMP中相同。

假如我們的節點是這樣的

// link 就是 fail 指針
const int MaxNode = 500000 + 50;
    const int CharSet = 26;
    int go[MaxNode][CharSet], link[MaxNode];
    int val[MaxNode]; // 附加信息,例如判斷是否爲一個單詞的結尾
    int nodes;

在AC自動機中輸入一個新的字符c,轉移時會先沿着link指針走,遇到 go[c] not equal 0 的節點 或者到了根就會停下。
中間走的這一段都不存在c轉移,在構造的時候已經知道這個事實了,所以可以把這一段多跑的路給壓縮掉。
把原來不存在的轉移都補上
就再也沒有while了~~

    void build() {
        queue<int> q;
        q.push(0);
        while ( !q.empty() ) {
            int u = q.front(); q.pop();
            int fa = link[u];
            for (int i = 0; i < CharSet; ++i) {
                int v = go[u][i];
                if ( v ) {
                    link[v] = u ? go[fa][i] : 0;
                    q.push(v);
                } else {
                    go[u][i] = go[fa][i];
                }
            }
        }
    }
    init();
    ac_insert("hers");
    ac_insert("she");
    ac_insert("he");
    ac_insert("s");
    get_draw(root, "");
    ac_construct();
    match("she");
    return 0;

改進後fail指針的指向
這裏寫圖片描述

一開始寫的是指針形式的,也是預先分配內存

struct Node {
    Node *go[CharSet], *link;
    int count;
};

不過指針的開銷貌似會更大? 雖然看起來是一樣的。。
hdu 2222 一直 MLE
所以實際的板子改成了開數組的

用last指針進一步提高轉移效率

用last指針表示沿着link走,遇到的第一個爲某個單詞結尾的節點。
這樣在match的時候,沿着 last 走而不是 link,遇到的都是單詞節點

    void build() {
        queue<int> q;
        q.push(0);
        while ( !q.empty() ) {
            int u = q.front(); q.pop();
            int fa = link[u];
            for (int i = 0; i < CharSet; ++i) {
                int v = go[u][i];
                if ( v ) {
                    link[v] = u ? go[fa][i] : 0;
                    last[v] = val[link[v]] ? link[v] : last[link[v]];
                    q.push(v);
                } else {
                    go[u][i] = go[fa][i];
                }
            }
        }
    }

我的模板

namespace Trie {
    const int MaxNode = 500000 + 50;
    const int CharSet = 26;
    int go[MaxNode][CharSet], link[MaxNode], last[MaxNode];
    int val[MaxNode];
    int nodes;

    void init() {
        nodes = 1;
        memset(go[0], 0, sizeof(go[0]));
    }

    int allocate() {
        memset(go[nodes], 0, sizeof(go[nodes]));
        val[nodes] = 0;
        last[nodes] = 0;
        link[nodes ++] = 0;
        return nodes - 1;
    }

    void add( const char* s ) {
        int cur = 0;
        for (; *s; ++ s) {
            int id = *s - 'a';
            if ( !go[cur][id] ) {
                go[cur][id] = allocate();
            }
            cur = go[cur][id];
        }
        val[cur] += 1;
    }

    int match(const char* s) {
        int cur = 0, ret = 0;
        for (; *s; ++s) {
            cur = go[cur][*s-'a'];
            int tmp = cur;
            if ( val[tmp] ) {
                ret += val[tmp], val[tmp] = 0;
            }
            while ( last[tmp] ) {
                tmp = last[tmp];
                if ( val[tmp] ) {
                    ret += val[tmp], val[tmp] = 0;
                }
            }
        }
        return ret;
    }

    void build() {
        queue<int> q;
        q.push(0);
        while ( !q.empty() ) {
            int u = q.front(); q.pop();
            int fa = link[u];
            for (int i = 0; i < CharSet; ++i) {
                int v = go[u][i];
                if ( v ) {
                    link[v] = u ? go[fa][i] : 0;
                    last[v] = val[link[v]] ? link[v] : last[link[v]];
                    q.push(v);
                } else {
                    go[u][i] = go[fa][i];
                }
            }
        }
    }
}

構造不包含模式的串

當 自動機 構造好後,我們有了 r 個狀態。那麼就存在一個 r x r的矩陣 T,T(i,j) 表示狀態 i 轉移到狀態 j 的方法數
T 可以這樣構造

// 輸入 (當前狀態,輸入) 
// 返回轉移到的狀態, -1 表示無效轉移
int check(int st, int input) {
        st= go[st][input];
        // 如果當前節點或其後綴是模式串,則爲無效轉移
        if ( tag[st] || last[st] ) return -1;
        return st;
}

Mat T(r); // r 爲狀態總數
memset(T.m, 0, sizeof(T.m));
for (int i = 0; i < n; ++i) {
    for (int j = 0; j < CharSet; ++j) {
        int st = check(i, j);
        if ( st != -1 ) T.m[i][st] += 1;
    }
}

稱不包含模式的串爲合法串
矩陣 A0=(1,0...0) 一共 r 個元素
A1=A0T ,構造出長度爲1的合法串
An=A0Tn ,結合矩陣快速冪,就可以構造出長度爲n的合法串

例題

POJ 2778 DNA Sequence
詳解可以看這篇

發佈了278 篇原創文章 · 獲贊 10 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章