后缀自动机练习专题

后缀自动机练习专题

一些比较有用的东东:

  • (1) \(\text{sam}\) 上一条从初始状态出发的路径对应一个子串

  • (2) \(\text{parent}\) 树上一个节点能表示的最长的串对应一个前缀/后缀

  • (3) \(len(u)\) 表示节点 \(u\) 能表示的最长串的长度

  • (4) \(fa(u)\) 表示节点 \(u\) 的后缀链接指向的节点,也就是其在 \(\text{parent}\) 树上的父亲

  • (5) 表示两个后缀的公共前缀的节点是两个后缀在 \(\text{parent}\) 树上的 \(lca\)

  • (6) \(R(u)\) 表示节点 \(u\)\(\text{right}\) 集合,\(sz(u)\) 表示 \(R(u)\) 的大小,\(R(u) \subset R(fa(u)), sz(u) < sz(fa(u))\)

  • (7) 广义 \(\text{sam}\) 可以理解为一个 \(\text{sam}\) 维护多个串,每一次插入完一个串后将 \(tail\) 指针设为初始状态


## SPOJ 1811 Longest Common Substring

给出两个串 \(s, t\),求这 \(s, t\) 的最长公共子串,\(|s|, |t| \leq 2.5 \times 10^5\)

​ 对于 \(s\)\(\text{sam}\) ,根据 (1) 可以将 \(t\) 放进 \(\text{sam}\) 匹配,当失配时相当于要要从当前匹配到的串的一个后缀继续开始匹配,等价于在 \(\text{sam}\) 上跳 \(fa(u)\) 。可以证明这样一定能匹配到最长公共子串。


## SPOJ 1812 Longest Common Substring II

给出 \(n\) 个串,求这个 \(n\) 个串的最长公共子串,\(1 \leq n \leq 10, |s| \leq 10^5\)

​ 和上题类似的, 还是取出一个串建 \(\text{sam}\),可以求出每一个节点被多少个串匹配到了,那么答案就是 \(\max(len(u))\)\(u\) 是被 \(n - 1\) 个串匹配到的节点。考虑每一次匹配到某个节点的时候,根据 (6),其祖先代表的串也会被匹配到,所以每一个串匹配完以后还要更新其祖先的匹配次数,暴力向上跳即可。

​ 另外一种做法,考虑串数只有 \(10\) ,可以建一个广义 \(\text{sam}\) 将所有串放进去,用二进制维护 \(R(u)\) 是否包含来自某个串的后缀,当一个节点包含来自所有串的后缀时,其就可以对答案产生贡献。当 \(n\) 比较大的情况可以用 \(\text{bitset}\) 或者线段树合并来维护。


## SPOJ 7258 Lexicographical Substring Search

给出一个串 \(s\),以及\(q\) 次询问,每次询问 \(s\) 中第 \(k\) 小的子串,重复的子串仅算一次, \(|s| \leq 9 \times 10^4, q \leq 500\)

​ 可以不考虑 \(\text{parent}\) 树,只考虑 (1) ,计算出每一个节点向下走能走到的路径数量。由于要求的是字典序第 \(k\) 小,每一次二分出从这个节点出发应该走的转移边的字符是什么,向下走即可。这样做复杂度是 \(O(|s|q)\),由于 \(q\) 不大,足以通过此题。

​ 实际上考虑 \(\text{parent}\) 可以进一步优化算法的复杂度,考虑原先的 \(\text{parent}\) 树一个节点代表的多个串都是最长的串的一个后缀,是一棵类似于前缀树的结构,这样不能适用于一些字典序上优美的性质。不妨将串反序插入到\(\text{sam}\) 中,这样每一个点能代表的多个串都是最长的串的前缀,这些串从长到短在字典序上一定是有序的。扩展到整棵树上,根据 \(mn(u) = len(fa(u)) + 1\) ,每个点代表的字符串都比其祖先代表的字符串的字典序大。于是可以计算出每一棵子树代表了多少串,在 \(\text{dfn}\) 序上二分答案即可,复杂度是 \(O(qlogn)\)


## 「TJOI2015」弦论

给出一个串 \(s\) ,根据题目要求来求出相同子串算一个或多个的第 \(k\) 小子串 \(|s| \leq 5 \times 10^5\)

​ 第一问和上一题完全等价,考虑第二问的情况,一个子串在 \(s\) 中的出现次数就是其对应 \(\text{parent}\) 树节点的 \(\text{right}\) 集合大小,也就是 \(sz(u)\) ,只需要对于每一条路径把权值为 \(sz(u)\) 进行统计即可。


## Codechef January Challenge 2018 - Killjee and k-th letter

给出一个的串 \(s\),将 \(s\) 所有子串按照字典序排列好相接起来形成一个新串,\(q\) 次询问,每一次询问问新串中的第 \(k\) 个字符是什么,强制在线。\(|s|, q \leq 2 \times 10^5\)

​ 考虑上上题给基于 \(\text{parent}\) 的做法,反序插入后每个节点代表的字符串在 \(\text{dfn}\) 序上是有序的,所以只要求出每一个节点代表的串的数量,然后对 \(\text{dfn}\) 序进行二分求出答案在哪个节点代表的子串上。考虑一个子串其所代表的串长度是从 \([len(fa(u))+1,len(u)]\) 连续的,可以直接求出 \(k\) 对应的子串是从节点 \(u\) 对应的后缀的多少位,查找该位置在原串上的字符即可

Codeforces 873 F. Forbidden Indices

有一个串 \(s\)\(s\) 的有些位置是非法的,求所有不以非法位置为结尾的子串 \(a\)\(\sum|a|\times tot(a)\) 的值,其中 \(tot(a)\) 表示 \(a\)\(s\) 中的出现次数。\(|s| \leq 2 \times 10^5\)

\(\text{parent}\) 树上一个节点代表的串的总数就是 \((len(u)-len(fa(u))) \times sz(u)\) ,维护出每一个串不含非法位置的 \(\text{right}\) 集合大小后直接求和


## 「AHOI2013」差异

给出一个串 \(s\) ,对于其所有后缀 \(t_i, t_j\) ,求 \(\sum len(t_i)+len(t_j) - 2len(lcp(t_i, t_j))\) 的值, \(|s| \leq 5 \times 10^5\)

​ 考虑将串反序插入到 \(\text{parent}\) 树后,两个后缀节点的 \(\text{lcp}\) 就是他们的 \(\text{lca}\) ,那么可以树形 \(\text{dp}\) 出以每一个节点作为 \(\text{lca}\) 时得到的 \(\sum len(t_i)+len(t_j) - 2len(u)\) ,最后对所有节点求和就是答案


## 「NOI2015」品酒大会

给出一个串 \(s\) 和一个序列 \(a\) ,对于所有 \(i\) ,求出 \(lcp(x, y) \geq i\) 的方案数以及满足条件的最大的 \(a_x \times a_y\) \(|s| \leq 3 \times 10^5|a_i| \leq 10^9\)

​ 本质上和上一题做法一样,考虑第一问只需要求出对于每一个节点 \(u\) ,在其子树里选出两个节点 \(\text{lca}\)\(u\) 的方案数即可,第二问稍有复杂,因为权值可正可负,需要分类讨论 \(\text{dp}\) 维护最大值和最小值,便于合并相乘时统计答案


## 「BZOJ3473」字符串 (双倍经验=3277)

给出 \(n\) 个字符串,求有多少个子串是其中至少 \(k\) 个字符串的子串,\(\sum|s| \leq 10^5, 1\leq k \leq n\)

​ 建立一个广义 \(\text{sam}\) ,每次插入一个串后维护一下 \(\text{parent}\) 树上哪些节点的 \(\text{right}\) 集合已经拥有了来自这个串的后缀,这样每一个新增的插入节点都需要向祖先更新这个信息,根据均值不等式分析,插入所有串的总复杂度是 \(O(|s|\sqrt|s|)\)

​ 考虑可以直接用线段树合并维护每一个节点的 \(\text{right}​\) 集合有哪些串的后缀,全部建完以后向上合并更新祖先的答案,总复杂度 \(O(|s|log|s|)​\)


## 「ZJOI2015」诸神眷顾的幻想乡

有一棵 \(n\) 个节点的树,每个节点上有一个字符,现在把每一条简单路径看成一个串,求本质不同的子串数量,\(n \leq 10^5\) 树的叶子节点数量 \(\leq 10\)

​ 如果一条简单路径被另外一条简单路径包含,那么其代表的串完全可以看成另外一个串的子串,所以我们只需要考虑不被任何串包含的简单路径,其数量显然是叶子节点个数的平方。于是暴力将这些串插入到广义 \(\text{sam}\) 中对不同子串数量计数即可。


## 「SDOI2016」生成魔咒

一开始有一个空串,一共有 \(n\) 次操作,每一次操作在这个串末尾加一个字符,求每一次操作结束时串中不同子串的数量 \(n \leq 10^5\)

\(\text{sam}\) 是在线构造的,每一次插入节点 \(p\) 后维护一下 \(len\) 发生改变的节点对答案的贡献即可


## 「BZOJ2555」 Substring

在当前字符串的后面插入一个字符串
询问字符串 \(s\) 在当前字符串中出现了几次?(作为连续子串) 你必须在线支持这些操作。

​ 丧心病狂的一道题,由于在线插入串到 \(s\) 后还要维护每一个点的 \(sz(u)\) ,所以要动态支持给 \(\text{parent}\) 树加边,维护子树大小可以转为链加,用 \(lct\) 来维护即可


## Codeforces 666 E. Forensic Examination

给你一个母串和很多询问串,每次询问求母串的一段区间在一段连续询问串中出现最多的询问串的出现次数和编号 \(1 \leq |s|, q \leq 5 \times 10^5\)

​ 对所有串建一个广义 \(\text{sam}\) ,每次询问倍增找到左端点在 \(\text{parent}\) 树上对应的节点,用线段树合并维护出每个节点的 \(\text{right}\) 集合中每个询问串的出现次数,区间查询 \(\max\) 即可


## 「TJOI / HEOI2016」字符串

萌萌哒的传送门= =


## 「NOI2018」你的名字 (非正解)

萌萌哒的传送门= =


## 「BJWC2018」 Border的四种求法

萌萌哒的传送门= =


posted @ 2018-10-09 15:10  Joyemang33  阅读(1398)  评论(0编辑  收藏  举报