字符串 _ AC自动机

概述

算法作用:
O(n)的复杂度 求出多个匹配串 出现在 模式串的 哪些地方 出现次数。

算法核心:
自动机的和kmp 类似,关键是next指针

构建next指针

next指针:状态 u 的 next 指针指向另一个状态 v, 当且仅当 v 是 u 的最长后缀。
即: next 指针是指向所有模式串的前缀中匹配当前状态的最长后缀的状态。

非平凡的后缀:除了自身以外的后缀。
上面的后缀都是非平凡的。

Trie 中的结点表示的是某个模式串的前缀。我们在后文也将其称作状态
一个结点表示一个状态,Trie 的边就是状态的转移。

如图:
image

如何求next呢? 和kmp类似:

如果节点 u 的父亲节点是fa,且fa通过字符 c 的边指向 u ;父亲节点fa的next指向k。
如果我们要找u的next指针,那么我们就看k的子节点

  • 如果 \(trie[k][c]\) 存在:u 的 \(next\) 就指向 \(trie[k][c]\)

  • 如果 \(trie[k][c]\) 不存在:就 while(u=next[u]),直到找到相同的或者根节点为止。将u 的 \(next\) 就指向 \(trie[k][c]\)

代码
我们可以发现我们是用前面层的信息去 更新 新的一层的信息,所以next需要一层一层的获取,因此用bfs。

void build(){
    queue<int> q;
    //从根的所有儿子开始搜索
    for(int i=0;i<26;i++){
        if(tr[0][i])
            q.push(tr[0][i]);
    }
    while(q.size()){
        int t=q.front();
        q.pop();
        for(int i=0;i<26;i++){
            int c=tr[t][i];
            if(!c) continue;//如果节点不存在continue

            int j=ne[t];
            //如果next指针指到的位置的儿子不存在;
            //这个字符那么就一直往回找直到找到为止
            while(j && !tr[j][i]) j=ne[j];
            //next数组等于找到的节点j的next
            if(tr[j][i]) j=tr[j][i];
            ne[c]=j;
            q.push(c);
        }
    }
}

匹配过程

和kmp类似:查找过程和构建过程差不多。
注意几点:

  1. 如果一个字符串匹配成功,那么他的后缀也一定可以匹配成功。
    比如说:成功匹配到she:那么所有she的后缀(he,e)也一定可以匹配

  2. 为了避免重复计算,我们将经过的cnt[u]设为0。
    cnt数组是有多少以此节点为结尾的匹配串

代码:

    cin>>str;
    int ans=0;
    for(int i=0,j=0;str[i];i++){
        int t=str[i]-'a';
        //
        while(j && !tr[j][t]) j=ne[j];
        if(tr[j][t]) j=tr[j][t];
        int p=j;
        //回溯匹配串的后缀
        while(p){
            ans+=cnt[p];
            cnt[p]=0;//cnt数组是有多少以此节点为结尾的匹配串
            p=ne[p];
        }
    }
cout<<ans<<endl;

Trie图优化

我们可以发现代码上面里面有个while循环,虽然while还是常数,但是我们希望可以优化掉。

证明while是常数
待补

优化思路:
在没有匹配时 把while循环多次跳 优化为 直接跳到ne指针最终跳到的位置。从而去掉while循环。

优化方式: 直接记住吧
每次取出队首的结点u (fail[u]在之前的 BFS 过程中已求得),然后遍历字符集u的各个子节点:

  1. 如果 \(trans[u][i]\) 存在,我们就将 \(trans[u][i]\)next 指针赋值为 \(trans[next[u]][i]\)
  2. 否则,令trans[u][i] 指向 \(trans[next[u]][i]\) 的状态。

oiwikl的解释

image

严格鸽 的解释
image

类似于并查集的路径压缩,我们可以建成Trie图。
(因为会使Trie原本的DAG结构出现环。)

构建代码:

void build(){
    queue<int> q;
    //从根的所有儿子开始搜索
    for(int i=0;i<26;i++){
        if(tr[0][i]) 
            q.push(tr[0][i]);
    }
    while(q.size()){
        int t=q.front();
        q.pop();
        for(int i=0;i<26;i++){
            int p=tr[t][i];
            if(!p) tr[t][i]=tr[ne[t]][i];
            else{
                ne[p]=tr[ne[t]][i];
                q.push(p);
            }
        }
    }
}

遍历时只需要直接访问即可

    int ans=0;
    for(int i=0,j=0;str[i];i++){
        int t=str[i]-'a';
        j=tr[j][t];

        int p=j;
        while(p){
            ans+=cnt[p];
            cnt[p]=0;
            p=ne[p];
        }
    }

原文1
原文2
原文3

模板代码

#include <bits/stdc++.h>
using namespace std;
const int N=10010,S=55;
int n;

string str;
int tr[N*S][26],cnt[N*S],idx;
int ne[N*S];
void insert(){
    int p=0;
    for(int i=0;i<str[i];i++)
    {
        int t=str[i]-'a';
        if(!tr[p][t]) tr[p][t]=++idx;
        p=tr[p][t];
    }
    cnt[p]++;
}
void build(){
    queue<int> q;
    for(int i=0;i<26;i++){
        if(tr[0][i]) 
            q.push(tr[0][i]);
    }
    while(q.size()){
        int t=q.front();
        q.pop();
        for(int i=0;i<26;i++){
            int p=tr[t][i];
            if(!p) tr[t][i]=tr[ne[t]][i];
            else{
                ne[p]=tr[ne[t]][i];
                q.push(p);
            }
        }
    }    
}
void solve(){
    memset(tr,0,sizeof  tr);
    memset(cnt,0,sizeof cnt);
    memset(ne,0,sizeof ne);
    idx=0;
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>str;
        insert();
    }
    build();
    cin>>str;
    int ans=0;
    for(int i=0,j=0;str[i];i++){
        int t=str[i]-'a';
        j=tr[j][t];

        int p=j;
        while(p){
            ans+=cnt[p];
            cnt[p]=0;
            p=ne[p];
        }
    }
cout<<ans<<endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;cin>>t;
    while(t--) 
        solve();
    return 0;
}

应用

posted @ 2022-08-09 10:32  kingwzun  阅读(28)  评论(0编辑  收藏  举报