AC自动机

前置芝士:Trie 树,KMP算法。

概述

AC 自动机是建立在 Trie 树上的自动机,一般拥有两个步骤:构建 Trie 与构造失配指针。它可以解决多模式串匹配问题。

建立

首先,将模式串插入到 Trie 树中。然后,考虑字典树中的一个节点 \(u\),它的父节点为 \(fa\)\(fa\) 通过字符 \(c\) 的边指向 \(u\)。令上面一句话表示为 \(trie(fa,c)=u\),其失配指针指向的节点表示为 \(fail(u)\)。假设深度小于 \(u\) 的失配指针都已经求得,则分三类情况:

  1. 若存在节点 \(v\) 使得 \(trie(fail(fa),c)=v\),则 \(fail(u)=v\)
  2. 若上述前提不存在,则不断跳失配指针。即第一次跳到 \(trie(fail(fail(fa)),c)\),第二次跳到 \(trie(fail(fail(fail(fa))),c)\),直到跳到根节点。
  3. 若上述过程中未出现存在的节点,则 \(fail(u)\) 为根节点。

至此,失配指针的搭建完成。

例题一

P3808 AC 自动机(简单版)
给定 \(n\) 个模式串 \(s_i\) 和一个文本串 \(t\),求有多少个不同的模式串在文本串里出现过。

解法

AC 自动机裸题,非常适合 AC 自动机入门。代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+10;
struct AC//自动机结构体
{
    int fail,ch[26],end;
}t[MAXN];
int tot=0,n;
int id(char c)
{
    return c-'a';
}
void insert(string s)//Trie 构建
{
    int now=0;
    for(int i=0;i<s.size();i++)
    {
        if(!t[now].ch[id(s[i])]) t[now].ch[id(s[i])]=++tot;
        now=t[now].ch[id(s[i])];
    }
    t[now].end++;
}
void get()//构建 fali
{
    queue<int> q;
    for(int i=0;i<26;i++)
        if(t[0].ch[i])
        {
            t[t[0].ch[i]].fail=0;
            q.push(t[0].ch[i]);
        }
    while(!q.empty())//bfs
    {
        int tmp=q.front();
        q.pop();
        for(int i=0;i<26;i++)
        {
            if(t[tmp].ch[i])
            {
                t[t[tmp].ch[i]].fail=t[t[tmp].fail].ch[i];
                q.push(t[tmp].ch[i]);
            }
            else t[tmp].ch[i]=t[t[tmp].fail].ch[i];//子结点指向fail的子结点
        }
    }
}
int query(string s)//AC 自动机匹配
{
    int now=0,res=0;
    for(int i=0;i<s.size();i++)
    {
        now=t[now].ch[id(s[i])];
        for(int j=now;j&&t[j].end!=-1;j=t[j].fail)
        {
            res+=t[j].end;
            t[j].end=-1;
        }
    }
    return res;
}
int main(  )
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        string s;
        cin>>s;
        insert(s);
    }
    get();
    string s;
    cin>>s;
    cout<<query(s);
}

例题二

P5357 【模板】AC 自动机
给定文本串 \(S\)\(n\) 个模式串 \(T_i\),求每个模式串在文本串中的出现次数。
\(1\le n\le 2\times 10^5,\sum\mid T_i\mid\le 2\times 10^5,\mid S\mid\le 2\times 10^6\)

解法

多模式串匹配,考虑 AC 自动机。但是一个 AC 自动机莽过去就会 TLE,考虑优化。
发现 AC 自动机上节点入度多而出度为 \(1\),故考虑每个点只访问一次。如此一来容易想到拓补排序。但由于出度为 \(1\),所以无需建图,直接跳即可。另外,代码中采用的是建图的方法。
代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;
string s[MAXN],T;
int fail[MAXN],ch[MAXN][26],tot,id[MAXN],sum[MAXN];
vector<int> G[MAXN];
int insert(string s)
{
    int tmp=0;
    for(auto &c:s)
    {
        int i=c-'a';
        if(!ch[tmp][i]) ch[tmp][i]=++tot;
        tmp=ch[tmp][i];
    }
    return tmp;
}
void bfs()//建立AC自动机
{
    queue<int> q;
    for(int i=0;i<26;i++) if(ch[0][i]) q.push(ch[0][i]);
    while(!q.empty())
    {
        int tmp=q.front();
        q.pop();
        for(int i=0;i<26;i++)
        {
            if(ch[tmp][i])
            {
                fail[ch[tmp][i]]=ch[fail[tmp]][i];
                q.push(ch[tmp][i]);
            }
            else ch[tmp][i]=ch[fail[tmp]][i];
        }
    }
}
void dfs(int x)
{
    for(auto &i:G[x])
    {
        dfs(i);
        sum[x]+=sum[i];
    }
}
int main(  )
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>s[i];
        id[i]=insert(s[i]);//建树
    }
    bfs();
    cin>>T;
    for(int i=1;i<=tot;i++) G[fail[i]].push_back(i);
    int tmp=0;
    for(auto &c:T)
    {
        int i=c-'a';
        tmp=ch[tmp][i];
        sum[tmp]++;
    }
    dfs(0);//求解
    for(int i=1;i<=n;i++) cout<<sum[id[i]]<<'\n';
}

持续更新ing……

posted @ 2026-07-13 16:51  duchuyuanX  阅读(13)  评论(0)    收藏  举报