AC 自动机
要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!要去流光协奏上海场啦!
AC 自动机
用于多模式串匹配问题,通常与 dp 结合。
自动机很好玩的。感觉越来越喜欢串串题了呢。
天依宝宝可爱!
洛谷 P3808
思维难度:\(\color{#FE4C61} 红\) *800
板子题。
AC 自动机,可以理解为利用 Trie 在多个串上跑 KMP。
首先记住我们要求的是一个文本串 \(t\) 中包含了几个模式串 \(s_i\)、分别包含了多少次等信息。
重点在于构建 fail 指针,节点 \(u\) 的 fail 指针 \(fail_u\) 指向 \(u\) 表示的字符串的最长 border(并不严谨,因为此处指所有模式串中的 border)。
令 \(p_u\) 为 \(u\) 的父亲,\(c\) 为连接 \(p_u,u\) 的边。那么类似于 KMP,一直跳 \(p_u\) 的 fail 直到跳到一个 \(v\) 满足 \(v\) 有字符 \(c\) 这个儿子,此时 \(fail_u\) 就是 \(v\) 的这个儿子。边界条件是跳到根结点,我们可以认为根结点的 \(|\Sigma|\) 个儿子都存在,且它们的 fail 指针都指向根结点。
但是这样复杂度太不优秀了,每次都要重复地跳 fail,所以考虑记忆化。当一个节点 \(v\) 没有 \(c\) 这个儿子的时候,我们令 \(v\) 的边 \(c\) 指向 \(fail_v\) 的儿子 \(c\),这样就可以一步到位,直接由 \(u\) 跳到 \(fail_u\)。
那 \(fail_v\) 的儿子 \(c\) 如果不存在怎么办呢?这很不好处理,所以要避免。具体地,可以发现 \(fail_v\) 在 Trie 中的深度一定小于 \(v\) 的深度,所以可以通过 bfs 来建 fail,这样就不会出现上述情况了。
匹配文本串是简单的,参照 KMP 的思想,对于文本串的每一个前缀,都匹配一遍,也就是跳一遍 fail,那么显然满足跳到的每一个点都是当前前缀的后缀,都加上就好了。当遇到文本串的某个前缀不在 Trie 中的时候,就让文本串末尾的那个指针 pos 跳一次 fail。
构建 fail 指针的时间复杂度是 \(O(|\Sigma| \sum |S_i|)\),查询复杂度最优 \(O(|T|)\)(利用 fail 树优化跳 fail 过程,后面 P5357 会写)。
天依宝宝可爱!
洛谷 P3796
思维难度:\(\color{#FE4C61} 红\) *800
这个是求每个模式串出现了多少次的板子。
天依宝宝可爱!
洛谷 P3121
思维难度:\(\color{#F39C11} 橙\) *1000
可以发现 AC 自动机匹配文本串的时候,正好是按照从前往后的顺序匹配的。
因为每次暴力跑文本串匹配再暴力删除不怎么优美,所以可以开个栈每次把新字符压入栈顶,删的时候直接删栈顶的一些元素即可,当然需要记录栈内每个字符在 Trie 上的位置。
天依宝宝可爱!
洛谷 P5357
思维难度:\(\color{#F39C11} 橙\) *1000
可以发现,如果把所有 fail 指针拿出来,会组成一棵仅由 fail 指针构成的树,满足每个节点 \(u\) 的父亲是 \(fail_u\)。所以在匹配文本串的时候,就不用每个位置暴力跳一遍 fail 了,只需要打个标记,最后每个节点的出现次数就是这个节点的子树和,这个直接拓扑序求就好了。
天依宝宝可爱!
POJ 3691 | AC 自动机上 dp
思维难度:\(\color{#FFC116} 黄\) *1500
先给个终止点的定义,就是所有模式串的结束点 和 后缀包括模式串的点。其中前者是容易的;对于后者,可以发现这正好是 fail 指针的定义,也就是说,如果一个节点的 fail 指针指向终止点,那么这个点也是终止点,否则不是(毕竟 fail 指针已经是最长 border 了)。所以 dfs 求即可。
题目要求 \(s\)(文本串)不包含任何模式串,这就相当于在 AC 自动机上不经过任何终止点。考虑 dp,令 \(f_{i,j}\) 为 \(s\) 的前 \(i\) 位已经匹配好,当前在自动机上的节点 \(j\) 时的最小代价,转移考虑下一个字符选什么即可,如果不是终止点就转移,否则跳过。
达成成就:炸掉 poj。
注意到 link 中的 #25005292,多刷新几次会发现 ta 在 Waiting、Compiling、Running & Judging 之间反复横跳。
现在好了,不过还有珍贵影像(,link,密码 356f。
我也不知道我是怎么做到的……代码
天依宝宝可爱!
洛谷 P2444
思维难度:\(\color{#F39C11} 橙\) *1000
显然存在无限长安全串的充要条件就是删掉终止点的 Trie 图上存在环。
天依宝宝可爱!
洛谷 P5319
思维难度:\(\color{#FFC116} 黄\) *1300
01 分数规划是什么感觉是 J 组的东西我怎么没见过要倒闭了 /jk /jk
乘法太大,所以要转 ln,有:
那么就要使 \(\frac 1 c \sum _{i=1} ^c \ln w_i\) 最大。
这是一个 01 分数规划的形式,二分答案,令当前二分的答案为 \(x\),则若 \(\frac 1 c \sum _{i=1} ^c \ln w_i > x\),有:
于是就与 \(c\) 无关了。
所以就可以在 AC 自动机上 dp,令 \(f_{i,j}\) 为当前在自动机上的点 \(i\),匹配到了文本串的第 \(j\) 个位置,上面那个式子的最大值。转移是容易的。
注意要把每个节点 \(w_i\) 加上其 fail 的 \(w_i\),在建 fail 树的时候顺便处理了即可。因为一个位置可能对应多个后缀。嗯怎么会忘掉这个呢?AC 自动机太久没写导致的 /ll
天依宝宝可爱!

浙公网安备 33010602011771号