<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Aho-Corasick |</title><link>https://sichen-wang.github.io/tags/aho-corasick/</link><atom:link href="https://sichen-wang.github.io/tags/aho-corasick/index.xml" rel="self" type="application/rss+xml"/><description>Aho-Corasick</description><generator>HugoBlox Kit (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Sat, 24 Jun 2023 11:00:56 +0000</lastBuildDate><image><url>https://sichen-wang.github.io/media/icon_hu_9dfcb674aa885f38.png</url><title>Aho-Corasick</title><link>https://sichen-wang.github.io/tags/aho-corasick/</link></image><item><title>AC 自动机</title><link>https://sichen-wang.github.io/blog/ac-%E8%87%AA%E5%8A%A8%E6%9C%BA/</link><pubDate>Sat, 24 Jun 2023 11:00:56 +0000</pubDate><guid>https://sichen-wang.github.io/blog/ac-%E8%87%AA%E5%8A%A8%E6%9C%BA/</guid><description>&lt;h3 id="失配指针"&gt;失配指针&lt;/h3&gt;
&lt;p&gt;定义 fail 为广义的 \(\pi\) 函数，即对于 \(n\) 个字符串，\(fail_{i,j}=\max\limits_{k=1}^{n} \pi(S_{k}\#S_{i,1\sim j})\)。&lt;/p&gt;
&lt;p&gt;上面那个是一种定义，但事实上，在 \(\rm AC\) 自动机中，fail 是具有指向性的，它指向的是所有模式串中，前缀与当前字符串某个前缀的后缀匹配可以产生最大匹配的那个串，它与当前串匹配的最后一个结点。&lt;/p&gt;
&lt;h3 id="构建-ac-自动机"&gt;构建 AC 自动机&lt;/h3&gt;
&lt;p&gt;首先对所有模式串建立 Trie 树，显然 Trie 树上的每一个结点都对应某个模式串的某个前缀。&lt;/p&gt;
&lt;p&gt;然后将每个 fail 都指向匹配的结点，显而易见，所有的 fail 都指向一个祖先节点，这个性质后面要用到。&lt;/p&gt;
&lt;p&gt;最后构建转移边 Auto，Auto 指向跳 fail 过程中存在字符 \(c\) 的结点。&lt;/p&gt;
&lt;p&gt;具体来说，可以以 \(\rm BFS\) 序遍历 Trie 树，在遍历的同时更新答案，转移式如下：&lt;/p&gt;
\[
\begin{cases}
fail_u = Auto_{fail_{fa_u}, c} \\
Auto_{u, c} = Auto_{fail_u, c}
\end{cases}
\]&lt;p&gt;建 \(\rm AC\) 自动机复杂度为 \(\Theta(|\Sigma|n)\)。&lt;/p&gt;
&lt;h3 id="文本串匹配"&gt;文本串匹配&lt;/h3&gt;
&lt;p&gt;遍历文本串，直接按照转移边走即可，如果要统计每个模式串出现的次数，就将所有经过的点权值 \(+1\)，再在 fail 树上差分即可计算。&lt;/p&gt;
&lt;p&gt;令模式串为 \(S\)，文本串为 \(T\)，总时间复杂度为 \(\Theta(|\Sigma||S|+|T|)\)。&lt;/p&gt;
&lt;p&gt;例题：
&lt;/p&gt;
&lt;p&gt;首先有一个显然的 \(\Theta(|s|\sum|t|)\) 暴力，枚举文本串的转移点，显然其不能超过最大串长，转移式为：&lt;/p&gt;
\[
f_i=\bigvee_{j=i-|s|}^{i-1}\{f_j\wedge[t_{j+1\sim i}\in s]\}
\]&lt;p&gt;这个式子的核心在于，\(f_{i}=true\) 当且仅当它存在一个后缀，这个后缀是某个模式串，并且删去这个后缀以后的文本串仍合法。&lt;/p&gt;
&lt;p&gt;于是考虑建立 \(\rm AC\) 自动机，将文本串放在自动机上跑的时候记 \(f_{i}\) 表示当前前缀是否合法。&lt;/p&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;存在如下 \(\rm AC\) 自动机性质：如果 \(v\) 可以从 \(u\) 开始由跳 fail 得到，即 fail 树上 \(v\) 是 \(u\) 的祖先，那么 \(v\) 代表的字符串是 \(u\) 代表的字符串的一个后缀。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;所以从 \(u\) 开始遍历跳 fail 经过的结点，如果它代表的字符串长度为 \(l\)，且为某个模式串的结尾，则将 \(g_{u,l}\) 记为 true。 由于 \(|s|\leqslant 10\)，所以只有当 \(l\leqslant 10\) 的时候 \(g_{u,l}\) 可能为 true，故直接状压成 \(g_{u}\)，其二进制下第 \(l\) 位表示之前 \(g_{u,l}\) 的值。&lt;/p&gt;
&lt;p&gt;最后直接将文本串放在 \(\rm AC\) 自动机上跑即可，记 \(now_{i}\) 表示文本串中当前点前 \(i\) 位的 \(f\) 值为多少，易知 \(i\leqslant 10\)，故也可以状压成一个数 \(now\)。 易知 \(f_{i}=true\) 当且仅当 \([now \operatorname{or} g_{u}]=true\)。&lt;/p&gt;
&lt;p&gt;总时间复杂度 \(\Theta(|\Sigma|n|s|^{2}+m|t|)\)。&lt;/p&gt;</description></item></channel></rss>