[复习] AC自动机

[复习] AC自动机

自动机

从一个状态通过接收一个信号转移到另一个状态。
其实就是从一个点走一种颜色的边到达另一个点,你会有一个初始点,然后每次走当前要走的颜色的边,会走到一个目标点,目标点保存着需要的答案。

AC自动机

\(Trie\) 为基础,\(kmp\) 的前缀函数思想构建的自动机。

用于解决多模式串匹配等任务。

例题:
给你一个文本串 \(S\),和 \(n\) 个模式串 \(T_i\),求每个模式串在文本串中出现了几次。
\(|S|\le 2*10^5,n\le2*10^5,\sum T_i\le2*10^5\)

我们不能做 \(n\)\(kmp\),所以把它“合成一次做”。

考虑把每个模式串插入 \(Tire\) 上。

每个节点是一个前缀,对于每个节点 \(S\) 维护这个 \(Trie\) 上最长的 \(S\) 的真后缀,即 \(border\),在这里我们称为失配指针 \(fail\)

\(tr[u][i]\) 表示 \(Trie\) 树的树边,\(fail[u]\) 为失配指针。

  • 对于 \(u\) 没有的边 \(i\),我们给它重新连向 \(tr[fail[u]][i]\)

  • 而如果有这条出边,那么得到 \(tr[u][i]\)\(fail\)\(tr[fail[u]][i]\)

我们在 \(trie\)\(bfs\) 从而得到这两个数组,当原本就有 \(tr[u][i]\) 时,将 \(trie[u][i]\) 加入队列。

for(int i=0;i<26;i++){
    if(trans[0][i])q.push(trans[0][i]);
}
while(!q.empty()){
    int u=q.front();q.pop();
    for(int i=0;i<26;i++){
        if(trans[u][i]){
            fail[trans[u][i]]=trans[fail[u]][i];
            q.push(trans[u][i]);
        }
        else trans[u][i]=trans[fail[u]][i];
    }
}

这里有个细节就是,第一个字符的 \(fail\) 一定为 \(0\),所以要先遍历根的儿子(就像前缀函数要从 \(2\) 开始跑一样)。

我们把 \(Trie\) 上没有的边都加上了,那现在 \(tr\) 数组就是一个转移函数,我们遍历文本串,每新加一个字符就走一条边,如此一来到达的节点就是当前文本串的前缀最长的在 \(Trie\) 上的后缀。

我们从一个点一直跳 \(fail\),如果跳到了一个模式串的末尾,那么说明这个模式串是当前文本串前缀的后缀,即是文本串的子串。

我们不用每次跳 \(fail\),这样时间复杂度是不正确的。

注意到 \(fail\) 是一棵树,我们可以每次在当前点打上标记,到最后再遍历树统计。

posted @ 2024-10-24 07:47  dengchengyu  阅读(2)  评论(0编辑  收藏  举报