QWQ

AC自动机

首先 你得先了解 KMP 和 Trie

在基于了解了以上两种算法后, 我们考虑构建和使用 AC自动机

AC自动机的本质是一颗 Trie 树,其独特之处在于利用了 KMP 的 Fail 指针

构建

定义 AC自动机

struct AC_aotomaton {
    int son[maxn][26]; // Trie 中结点
    int val[maxn]; // 是否为结束,有多少个单词
    int fail[maxn], last[maxn]; // 失配指针
    int sz;
    void clear() {
        memset(son[0], 0, sizeof(son[0])); sz = 1;
    }
    void clear_p(int x) {
        memset(son[x], 0, sizeof(son[x])); val[x] = 0;
    }
    int cg(char x) {return x - 'a';}
    // Trie树上添加结点
    void insert(char *s) {
        int u = 0;
        int len = strlen(s);
        for(int i = 0;i < len; ++i) {
            int c = cg(s[i]);
            if(!son[u][c]) {
                clear_p(sz); son[u][c] = sz++;
            }
            u = son[u][c];
        }
        val[u]++; // val[u] = (编号) 
    }
    //后面还有别的函数 - 用于求 fail 和匹配
}

构建 fail 指针

fail 指针 指向的结点代表的字符串当前结点字符串的最长后缀

那么我们想一想,当前结点最长的后缀在哪里,假设我们已经求出来当前结点的父亲的 fail 指针,对于当前结点来说,这一定是能找到的除了最后一位的 最长后缀

所以,如果 父亲结点的 fail 指针所指向的结点 $p$ 存在与当前结点字符相同的儿子, 那么当前结点的 fail 指针一定是指向结点 $p$ ,否则我们继续寻找指针的指针指向的结点,直到fail指针指向 原点0

于是,考虑如何构建 fail 指针,我们首先把 Trie 树分成一层一层的(姑且形象的定义吧), 那么对于一个结点的 fail 指针指向的结点, 深度一定小于这个结点的深度, 我们依次放入队列里面,就可以处理出所有结点的 fail 指针了

void getfail() {
    queue<int> q;
    fail[0] = 0;
    int u = 0;
    //Sigma_Size 字符集大小
    for(int i = 0;i < Sigma_Size; ++i) {
        u = son[0][i];
        if(u) {
            q.push(u); fail[u] = 0; last[u] = 0;
        }
    }
    while(!q.empty()) {
        int r = q.front(); q.pop();
        for(int i = 0;i < Sigma_Size; ++i) {
            u = son[r][i];
            if(!u) {
                son[r][i] = son[fail[r]][i]; continue;
            }
            q.push(u);
            int v = fail[r];
            while(v && (!son[v][i])) v = fail[v];
            fail[u] = son[v][i];
            last[u] = val[fail[u]]? fail[u]: last[fail[u]];
        }
    }
}

考虑如何查询

与 KMP 的做法类似,依次对每一个字符进行匹配,如果不能匹配,则转移到 fail 指针所在的结点,直到匹配完成或者 fail 指向原点(0)

int find(char *s) {
    int u = 0, cnt = 0;
    int len = strlen(s);
    for(int i = 0;i < len; ++i) {
        int c = cg(s[i]);
        u = son[u][c];
        int tmp = 0;
        if(val[u]) tmp = u;
        else if(last[u]) tmp = last[u];
        while(tmp) {
            cnt += val[tmp];
            val[tmp] = 0;
            tmp = last[tmp];
        }
    }
    return cnt;
}