AC自动机

P5357 【模板】AC 自动机 为例。

AC自动机是用于进行多模板串的匹配。既然是多模板串,当然是在字典树上构建的。作为自动机,就是根据规则不断在图上游走,最终走到需要的点。

插入时正常插入,记录模板串。

void ins(int len,int idn){
    int now=0;
    for(int i=1;i<=len;i++){
        int id=ch[i]-'a';
        if(t[now].son[id]==0){
            t[now].son[id]=++tot;
        }
        now=t[now].son[id];
    }
    t[now].v.push_back(idn);
}

想要优化到线性,核心在于失配时找到与后缀相同的前缀border。关键是 \(fail\)\(fail_i\) 指向当 \(i\) 的下一个节点失配时,最长border的从根节点出发的前缀末尾。如果失配,就找到 \(fail\) 的位置,思想和 KMP 一样。

\(fail\) 。画图,父亲节点的 \(fail\) 的节点会有相同的前缀。如果 \(fail_{fa}\) 有跟 \(i\) 相同字符的儿子,则 \(fail_i\) 就是那个相同字符的儿子。

匹配时,如果某个节点有字符 \(c\) 的儿子,可以让文本串在图上跑动。如果没有字符 \(c\) 的儿子,此时算是失配,但仍然要在图上跑动。失配后要找到 \(fail\) 的位置,可以直接将字符 \(c\) 的儿子设为 \(fail\) 的儿子。

可以据此建立 AC自动机。 \(fail\) 是从下向上指,所以要从根开始构建 \(fail\) 。广搜。

void workf(){
    queue<int>q;
    for(int i=0;i<=25;i++){
        if(t[0].son[i]!=0){
            q.push(t[0].son[i]);
        }
    }
    while(!q.empty()){
        int u=q.front();
        q.pop();
        for(int i=0;i<=25;i++){
            int son=t[u].son[i],fail=t[u].fail;
            if(son!=0){
                t[son].fail=t[fail].son[i];
                q.push(son);
            }
            else{
                t[u].son[i]=t[fail].son[i];
            }
        }
    }
    for(int i=1;i<=tot;i++){
        v[t[i].fail].push_back(i);
    }
}

匹配。如果到某个节点匹配成功时,所有用 \(fail\) 连接的节点都有相同的后缀,他们都匹配成功了。但不能暴力向上跳匹配(会超时),可以构建用 \(fail\) 连接的树,匹配到了打个标记,最后向上统计。

void qur(int len){
    int now=0;
    for(int i=1;i<=len;i++){
        int id=ch[i]-'a';
        now=t[now].son[id];
        f[now]++;
    }
}
void dfs(int x){
    for(auto i:v[x]){
        dfs(i);
        f[x]+=f[i];
    }
    for(auto i:t[x].v){
        ans[i]=f[x];
    }
}
posted @ 2026-08-03 13:09  TanHaoren  阅读(3)  评论(0)    收藏  举报