首页 > 其他分享 >AC自动机 AC-Automaton

AC自动机 AC-Automaton

时间:2025-01-23 10:33:52浏览次数:1  
标签:nxt AC now int texttt ns fail Automaton 自动机

更新日志 2025/01/23:开工。

概念

首先,你应该会KMP算法

AC自动机,本质上就是利用 KMP 的思想同时对多个模式串进行匹配。

具体的,我们将建立一棵 Trie,并在 Trie 上开展匹配。

思路

预处理(建Trie)

前面已经说过,我们将在 Trie 上开展匹配,所以首先我们将建一棵 Trie。

只要常规地把所有匹配串加入即可,记得记录每个匹配串对应的节点编号以统计答案。

失配处理(fail)

首先我们明确,一个节点表示一个状态,也就是说,如果我们当前位于 \(u\) 节点,那么当前状态就是 \(root\rightarrow u\) 的路径,表示这一条路径上的字符已经匹配完成。

\(fail\) 的概念与 KMP 中 border 重心概念下 \(nxt\) 的概念很接近,形式化的,\(fail_u\) 储存了 \(u\) 的存在的最长后缀状态。比如当前 Trie 中已有 \(\texttt{abcde}\) 和 \(\texttt{bcde}\) 和 \(\texttt{cde}\),那么 \(fail_{\texttt{abcde}}=\texttt{bcde}\)。

下面我们考虑如何得到 \(fail\),假如当前位于 \(u\),\(v=\mathrm{Trie}(u,\texttt{c})\),且 \(fail_u,\mathrm{Trie}(fail_u,\texttt{c})\) 已知,,那么:

\[fail_v\gets \mathrm{Trie}(fail_u,\texttt{c}) \]

但这是很片面的,我们需要考虑更全面的情况。

比如说,如果 \(\mathrm{Trie}(fail_u,\texttt{c})\) 不存在,那么显然:

\[fail_v\gets \mathrm{Trie}(fail_{fail_u},\texttt{c}) \]

直到至 \(root\) 仍不存在 \(\mathrm{Trie}(root,\texttt{c})\),那么:

\[fail_v\gets root \]

不难发现一个较为显然的优化,我们可以考虑路径压缩。

考虑如何保证 \(fail_u,\mathrm{Trie}(fail_u,\texttt{c})\),我们只需要 BFS 更新即可。因为 \(fail_u\) 的深度必然小于 \(u\) 的。

匹配

令进行匹配的字符串为 \(s\)。

从 Trie 的根节点开始,向下逐位匹配,如果当前状态存在对应的下一个状态,那么直接跳至对应状态即可,并更新对应状态的匹配答案数。

如果当前状态没有相应的下一个状态,那我们就跳 \(fail\),直到找到一个可以匹配的状态或者回到 \(root\)(匹配不上)为止。

不难发现,路径压缩同时可以优化匹配过程。

值得注意的是,在当前位置更新答案后,当前状态的所有后缀状态都要跟着更新,也就是一路跳 \(fail\) 至根节点路径上每个状态都得更新。

优化

路径压缩

在实现部分已经提到,那么如何具体实现呢?

我们更新 \(nxt_{u,\texttt{c}}\) 的定义,不再是 \(\mathrm{Trie}(u,\texttt{c})\),而是当前状态后面再加上 \(\texttt{c}\) 可以转移到的下一个匹配状态。

更具体的,如果存在 \(\mathrm{Trie}(u,\texttt{c})\),那么 \(nxt_{u,\texttt{c}}\gets \mathrm{Trie}(u,\texttt{c})\)。否则,\(nxt_{u,\texttt{c}}\gets nxt_{fail_u,\texttt{c}}\)。(\(nxt_{fail_u,\texttt{c}}\) 已经优先处理过了)

特殊的,若 \(nxt_{root,\texttt{c}}\) 不存在,\(nxt_{root,\texttt{c}}\gets root\)。

DFS优化

我们考虑优化更新当前状态的所有后缀状态的过程。

不难想到,我们可以先统计出每个点各自的答案,然后按顺序一路传递。

具体的,我们对于每一组 \(fail\),在图上从 \(fail_u\) 连向 \(u\),匹配完后 DFS 这个图,对当前节点的每一个子节点,都对其进行 DFS,并更新当前点答案。

由于每个点只有一个 \(fail\),所以每个点只会被遍历一次,复杂度是 \(O(n)\) 的。

模板

struct ac_automaton{
    struct node{
        int nxt[26];
        int ans,fail;
        void clear(){
            memset(nxt,0,sizeof(nxt));
            ans=fail=0;
        }
    }ns[L];
    int tot;
    vec<int> g[L];
    int insert(string s){
        int now=0;
        repl(i,0,s.size()){
            int &nxt=ns[now].nxt[s[i]-'a'];
            if(!nxt)nxt=++tot,ns[nxt].clear();
            now=nxt;
        }
        return now;
    }
    void build(){
        queue<int> q;
        repl(i,0,26)if(ns[0].nxt[i]){
            q.push(ns[0].nxt[i]);
            g[0].pub(ns[0].nxt[i]);
        }
        while(!q.empty()){
            int now=q.front();q.pop();
            repl(i,0,26){
                if(ns[now].nxt[i]){
                    ns[ns[now].nxt[i]].fail=ns[ns[now].fail].nxt[i];
                    g[ns[ns[now].fail].nxt[i]].pub(ns[now].nxt[i]);
                    q.push(ns[now].nxt[i]);
                }else ns[now].nxt[i]=ns[ns[now].fail].nxt[i];
            }
        }
    }
    void dfs(int now){
        for(int nxt:g[now]){
            dfs(nxt);
            ns[now].ans+=ns[nxt].ans;
        }
    }
    void query(string s){
        int now=0;
        repl(i,0,s.size()){
            now=ns[now].nxt[s[i]-'a'];
            ns[now].ans++;
        }
        dfs(0);
    }
};

例题

LG5357

代码
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef __int128 i128;
typedef double db;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
template <typename Type>
using vec=vector<Type>;
template <typename Type>
using grheap=priority_queue<Type>;
template <typename Type>
using lrheap=priority_queue<Type,vector<Type>,greater<Type> >;
#define fir first
#define sec second
#define pub push_back
#define pob pop_back
#define puf push_front
#define pof pop_front
#define chmax(a,b) a=max(a,b)
#define chmin(a,b) a=min(a,b)
#define rep(i,x,y) for(int i=(x);i<=(y);i++)
#define repl(i,x,y) for(int i=(x);i<(y);i++)
#define per(i,x,y) for(int i=(x);i>=(y);i--)

const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=998244353;

const int L=2e5+5;

struct ac_automaton{
    struct node{
        int nxt[26];
        int ans,fail;
        void clear(){
            memset(nxt,0,sizeof(nxt));
            ans=fail=0;
        }
    }ns[L];
    int tot;
    vec<int> g[L];
    int insert(string s){
        int now=0;
        repl(i,0,s.size()){
            int &nxt=ns[now].nxt[s[i]-'a'];
            if(!nxt)nxt=++tot,ns[nxt].clear();
            now=nxt;
        }
        return now;
    }
    void build(){
        queue<int> q;
        repl(i,0,26)if(ns[0].nxt[i]){
            q.push(ns[0].nxt[i]);
            g[0].pub(ns[0].nxt[i]);
        }
        while(!q.empty()){
            int now=q.front();q.pop();
            repl(i,0,26){
                if(ns[now].nxt[i]){
                    ns[ns[now].nxt[i]].fail=ns[ns[now].fail].nxt[i];
                    g[ns[ns[now].fail].nxt[i]].pub(ns[now].nxt[i]);
                    q.push(ns[now].nxt[i]);
                }else ns[now].nxt[i]=ns[ns[now].fail].nxt[i];
            }
        }
    }
    void dfs(int now){
        for(int nxt:g[now]){
            dfs(nxt);
            ns[now].ans+=ns[nxt].ans;
        }
    }
    void query(string s){
        int now=0;
        repl(i,0,s.size()){
            now=ns[now].nxt[s[i]-'a'];
            ns[now].ans++;
        }
        dfs(0);
    }
}aca;

const int N=2e5+5;

int n;
string s,t;
int id[N];

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
    cin>>n;
    rep(i,1,n)cin>>t,id[i]=aca.insert(t);
    aca.build();
    cin>>s;
    aca.query(s);
    rep(i,1,n)cout<<aca.ns[id[i]].ans<<"\n";
	return 0;
}

标签:nxt,AC,now,int,texttt,ns,fail,Automaton,自动机
From: https://www.cnblogs.com/HarlemBlog/p/18687230

相关文章

  • skypack,一个针对浏览器优化的cdn
    前言看solidjs的文档时,发现其cdn引入部分,使用的是一个没有见过的cdn。skypack。https://www.skypack.dev/软件包已针对浏览器使用进行了预先优化。页面问题其在国内的速度依然不算快,平均1s,和npmmirror之类的没法比。然而,这个cdn还比较独特,不知道如何从中下载文件,实在有......
  • 【亲测可用】Windows激活神器HEU KMS Activator v42.3.3
    软件介绍HEUKMSActivator,简洁高效的全能KMS/OEM激活工具,适用所有Windows,Office版本,无需联网即可一键激活,支持UEFI的KMS激活工具。KMS服务是微软对Windows,Office等产品的批量许可服务,利用KMS可以激活局域网内的产品。该工具利用KMS机制在系统搭建KMS服务器,从而实现在线或离......
  • Linux下卸载Oracle 11g
    第一种方法:使用oracle自带的runInstaller卸载123[oracle@VM_0_14_centosdeinstall]$cd$ORACLE_HOME[oracle@VM_0_14_centos 11.2.0]$cddeinstall/[oracle@VM_0_14_centosdeinstall]$./deinstall第二种方法:通过删除文件的方式卸载;(即:删除Oracl......
  • 回调函数 事件处理 dotnet .net 有界队列 背压机制(Backpressure)有界队列
    回调函数事件处理dotnet.net有界队列背压机制(Backpressure)有界队列通过有界队列来实现背压,确保生产者不会以超过消费者处理能力的速度发送数据。usingSystem.Threading.Channels;publicclassProgram{staticasyncTaskMain(string[]args){//创......
  • CF2063C Remove Exactly Two
    前言提供一个不需要分讨的\(O(Tnlogn)\)做法。解题思路首先会想到选出度数最大和次大的两个点删除。但是注意到,有三个度数都为最大的点连在一起的时候,你不能先删中间的点。(可以随便举个例子手玩一下。)这时有人就开始思考dp或者分类讨论了。这时候想我这种没有思维的人......
  • 使用 Java 和 Tesseract 进行验证码识别
    验证码(CAPTCHA)在网站和应用中被广泛用作防止自动化操作的验证机制。如果想要识别验证码,可以借助OCR(光学字符识别)技术实现自动识别。本文将介绍如何使用Java和TesseractOCR引擎来实现验证码的识别。环境准备安装TesseractOCR引擎Tesseract是一个开源的OCR引擎,必须......
  • Many Replacement
    正常思路就是暴力遍历,但是这样容易超时,所以就要优化代码。很容易想到,每种相同字母最终都会替换成一种字母,所以只要把26个字母最后替换成什么字母搞清楚,再用这种替换关系来替换所需字符串就好了。`#include<stdio.h>include<stdlib.h>include<string.h>intmain(){intn,q;ch......
  • 史上最强PDF工具-创建、编辑、加密、转换(PDF转word)、扫描和OCR-Adobe Acrobat Pro 202
    AdobeAcrobatPro是可跨多种设备使用的最全面、最现代的PDF解决方案。拥有25种PDF和电子签名工具。无论是企业办公、教育、法律还是个人使用,AdobeAcrobat都能提供高效、便捷、安全的文档处理体验。一、概述AdobeAcrobat是由Adobe公司开发的一款软件,它是用于创建、查......
  • 【Golang/gRPC/Nacos】在golang中将gRPC和Nacos结合使用
    Nacos与gRPC前言关于这部分,前段时间我在看文档以及视频教程的时候,怎么都想不明白,到底为什么要用gRPC是什么,他在项目中应该充当什么样的角色?Nacos又是如何和他结合的?于是我就决定去看看一些小项目是如何实现的这个功能,现在将我最近学到的分享给大家。正文在正文开始之前......
  • LibXL 4.5.1 for win/linux/Mac/iOS Patch
    DirectreadingandwritingExcelfilesLibXLisalibrarythatcanreadandwriteExcelfiles.Itdoesn'trequireMicrosoftExceland.NETframework,combinesaneasytouseandpowerfulfeatures.LibrarycanbeusedtoGenerateanewspreadsheetfro......