• AC 自动机


    AC 自动机

    比我小一届却吊打我的大脚玩家(djwj233)的博客

    AC 自动机内容与含义

    AC 自动机(Aho-Corasick automaton,abbr. ACAM),诞生于贝尔实验室。

    两个定义:

    • 文本串:匹配别人、包含其他串的串。
    • 模式串:被匹配、被包含的串。

    AC 自动机是一种多模匹配算法,就是解决多个模式串匹配单个/多个文本串用的。

    总的来说,AC 自动机类似将所有串变成一个连接所有串的 KMP。

    AC 自动机由 Trie 树、Fail 树和连接 Trie 和 Fail 的边组成。

    • Trie 树上的边表示一个串后面接上一个字符的前缀关系。
    • Fail 树上的边表示一个串前面接上一个串的后缀关系,即失配关系。
    • 连接 Trie 和 Fail 的边表示在不同串之间的的失配关系。

    通过 Trie 树和 Fail 树,AC 自动机可以用来为多个串同时匹配。一般我们先把最长的能够匹配的模式串(在 ACAM 上找出来)求出来,那么它沿着 Fail 树向上的所有串都被匹配了一次。

    AC 自动机建立

    首先对于所有模式串建立 Trie 树,找到他们的 Fail 指针,代码如下:

    vector<int> Fail_t[Maxn];
    int ch[Maxn][26],Fail[Maxn]/*,cnt[Maxn]*/;
    inline int Insert(char s[])
    {
    	 int len=strlen(s+1),x=0;
    	 for(int i=1;i<=len;i++)
    	 {
    	 	 if(ch[x][s[i]-'a']) x=ch[x][s[i]-'a'];
    	 	 else ch[x][s[i]-'a']=++tot,x=tot;
    	 }
    	 /*cnt[x]++;*/ return x;
    }
    inline void get_fail()
    {
    	 queue<int> q;
    	 for(int i=0;i<26;i++) if(ch[0][i]) q.push(ch[0][i]);
    	 while(!q.empty())
    	 {
    	 	 int cur=q.front(); q.pop();
    	 	 Fail_t[Fail[cur]].pb(cur);
    	 	 for(int i=0;i<26;i++)
    	 	 {
    	 	 	 if(ch[cur][i]) q.push(ch[cur][i]),
    	 	 	 	 Fail[ch[cur][i]]=ch[Fail[cur]][i];
    	 	 	 else ch[cur][i]=ch[Fail[cur]][i];
    		 }
    	 }
    }
    

    但有点时候如果字符集太大了怎么办?用 map

    假设如P2336 [SCOI2012]喵星球上的点名,字符总长 \(10^5\),字符集 \(10^4\),我们用了 map 也不能够记完所有 Trie 与 Fail 的连接,那就只能舍去这些边,只建出 Trie 和 Fail 树。

    map<int,int> Trie_t[Maxc];
    vector<int> Fail_t;
    void get_fail()
    {
    	 queue<int> q;
    	 for(auto v:Trie_t[0]) q.push(v.fi);
    	 while(!q.empty())
    	 {
    	 	 int cur=q.front(); q.pop();
    		 Fail_t[Fail[cur]].pb(cur);
    		 for(auto v:Trie_t[cur])
    		 	 q.push(v.se),Fail[v.se]=Trie_t[cur][v.fi];
    	 }
    }
    

    AC 自动机上的串匹配

    需要注意的是,ACAM 中的串都是模式串。

    P5357 【模板】AC 自动机(二次加强版)

    给定 \(n\) 个模式串 \(T_i\) 和文本串 \(S\),求每一个模式串 \(T_i\)\(S\) 中的出现次数。

    \(n\le 2\times 10^{5},|S|\le 2\times 10^{6},\sum{|T_i|}\le 2\times 10^{5}\)

    直接给 \(T\) 建立 ACAM,\(S\) 从根开始,每走到一个节点那么这各节点到根的路径上的每一个节点(都是一个串)都在 \(S\) 中出现一次,最终每个节点上的数值就是出现次数。

    由于每次都从这个节点到根标记会非常慢,所以在每个节点上标记,最终查询子树中标记个数就是出现次数。

    P2414 [NOI2011] 阿狸的打字机

    给定 \(n\) 个串 \(T_i\),有 \(m\) 次询问,每次询问第 \(i\) 个串在第 \(j\) 个串中的出现次数。

    \(n,m\le 10^{5},\sum{|T_i|}\le 10^{5}\)

    每次将 \(i\) 当做模式串,将 \(j\) 当做文本串,假设只有一次询问,我们就会仿照上一题的套路从根开始一路标记到 \(j\) 节点。由于这个题比较特殊,\(j\) 是 ACAM 上实际存在的点,只会经过 Trie 树上的边走到,所以一路标记下来恰好是根到 \(j\) 的一条路径。

    询问的时候直接查询以 \(i\) 为根的子树和即可。

    如果有多次询问?可以先把询问挂到文本串下面,以 dfn 序遍历 Trie 树,每次询问以 \(i\) 为根的子树和即可(树状数组维护)。

    AC 自动机上的 DP

    一般都是设 \(dp(i,j,\dots)\) 表示已经接了长度为 \(i\),当前走到第 \(j\) 个节点的状态及信息,有时还需要记下其他信息。

    P4052 [JSOI2007]文本生成器

    正难则反,之后仿照上面的 dp 格式基本上就做完了。

    P3311 [SDOI2014] 数数

    直接把数位 DP 扔上去就好了。

    AC 自动机杂题

    P2336 [SCOI2012]喵星球上的点名

    其中有一个常见技巧:

    给定 \(n\) 个模式串 \(T_i\)\(m\) 个文本串 \(S_i\),求出每个串 \(T_i\) 在多少个串 \(S_i\) 中出现过。

    直接查询 \(T_i\) 的子树和会导致算重,所以对于串 \(S_i\) 走到的节点序列 \(\{a\}\),按照 dfn 序排序,在相邻两个数的 \(Lca\) 处减去贡献,可以避免算重的情况。

  • 相关阅读:
    ASP.NET 如何取得 Request URL 的各個部分
    正则表达式
    sql server 存储过程中拼接sql,转义单引号
    C# 过滤敏感字符
    Facebook “Invite” 弹出窗口
    Silverlight 4 动态换Theme
    silverlight 4 com组件调用
    Silverlight 4 COM+ 操作支持示例集
    如何创建silverlight离开浏览器的应用程序
    Silverlight 4 的 WCF NET.TCP 协议
  • 原文地址:https://www.cnblogs.com/EricQian/p/15851117.html
Copyright © 2020-2023  润新知