OSDN Git Service

style: format markdown files with remark-lint
author24OI-bot <15963390+24OI-bot@users.noreply.github.com>
Fri, 2 Aug 2019 07:41:28 +0000 (03:41 -0400)
committer24OI-bot <15963390+24OI-bot@users.noreply.github.com>
Fri, 2 Aug 2019 07:41:28 +0000 (03:41 -0400)
docs/string/sam.md

index ea74723..571d78a 100644 (file)
@@ -307,23 +307,23 @@ void sam_extend(char c) {
 
 ### 额外信息
 
-观察 [实现](#_8) 中的结构体的每个变量。实际上,尽管 SAM 本身由 `next` 组成,但 SAM 构造算法中作为辅助变量的 `link` 和 `len` 在应用中常常比 `next` 重要,甚至可以抛开 `next` 单独使用。
+观察[实现](#_8)中的结构体的每个变量。实际上,尽管 SAM 本身由 `next` 组成,但 SAM 构造算法中作为辅助变量的 `link` 和 `len` 在应用中常常比 `next` 重要,甚至可以抛开 `next` 单独使用。
 
-设字符串的长度为 $n$,考虑 `extend` 操作中 `cur` 变量的值,这个节点对应的状态是 <u>执行 `extend` 操作时的当前字符串</u>,即字符串的一个前缀,每个前缀有一个终点。这样得到的 $n$ 个节点,对应了 $n$ 个不同的 **终点**。设第 $i$ 个节点为 $v_i$,对应的是 $S_{1 \ldots i}$,终点是 $i$。姑且把这些节点称之为“终点节点”。
+设字符串的长度为 $n$ ,考虑 `extend` 操作中 `cur` 变量的值,这个节点对应的状态是<u>执行 `extend` 操作时的当前字符串</u>,即字符串的一个前缀,每个前缀有一个终点。这样得到的 $n$ 个节点,对应了 $n$ 个不同的 **终点** 。设第 $i$ 个节点为 $v_i$ ,对应的是 $S_{1 \ldots i}$ ,终点是 $i$ 。姑且把这些节点称之为“终点节点”。
 
-考虑给 SAM 赋予树形结构,树的根为 0,且其余节点 $v$ 的父亲为 $link(v)$。则这棵树与原 SAM 的关系是:
+考虑给 SAM 赋予树形结构,树的根为 0,且其余节点 $v$ 的父亲为 $link(v)$ 。则这棵树与原 SAM 的关系是:
 
-* 每个节点的终点集合等于其 **子树** 内所有终点节点对应的终点的集合。
+-   每个节点的终点集合等于其 **子树** 内所有终点节点对应的终点的集合。
 
 在此基础上可以给每个节点赋予一个最长字符串,是其终点集合中 **任意** 一个终点开始 **往前** 取 `len` 个字符得到的字符串。每个这样的字符串都一样,且 `len` 恰好是满足这个条件的最大值。
 
 这些字符串满足的性质是:
 
-* 如果节点 A 是 B 的祖先,则节点 A 对应的字符串是节点 B 对应的字符串的**后缀**
+-   如果节点 A 是 B 的祖先,则节点 A 对应的字符串是节点 B 对应的字符串的 **后缀** 
 
-这条性质把字符串所有前缀组成了一棵树,且有许多符合直觉的树的性质。例如,$S_{1 \ldots p}$ 和 $S_{1 \ldots q}$ 的最长公共后缀对应的字符串就是 $v_p$ 和 $v_q$ 对应的 LCA 的字符串。实际上,这棵树与将字符串 $S$ 翻转后得到字符串的压缩后缀树结构相同。
+这条性质把字符串所有前缀组成了一棵树,且有许多符合直觉的树的性质。例如, $S_{1 \ldots p}$ 和 $S_{1 \ldots q}$ 的最长公共后缀对应的字符串就是 $v_p$ 和 $v_q$ 对应的 LCA 的字符串。实际上,这棵树与将字符串 $S$ 翻转后得到字符串的压缩后缀树结构相同。
 
-每个状态 $i$ 对应的子串数量是 $len(i)-len(link(i))$(节点 $0$ 例外)。注意到 $link(i)$ 对应的字符串是 $i$ 对应的字符串的一个后缀,这些子串就是 $i$ 对应字符串的所有后缀,去掉被父亲“抢掉”的那部分,即 $link(i)$ 对应字符串的所有后缀。
+每个状态 $i$ 对应的子串数量是 $len(i)-len(link(i))$ (节点 $0$ 例外)。注意到 $link(i)$ 对应的字符串是 $i$ 对应的字符串的一个后缀,这些子串就是 $i$ 对应字符串的所有后缀,去掉被父亲“抢掉”的那部分,即 $link(i)$ 对应字符串的所有后缀。
 
 ## 应用
 
@@ -357,7 +357,7 @@ $$
 
 总时间复杂度为: $O(\left|S\right|)$ 。
 
-另一种方法是利用上述后缀自动机的树形结构。每个节点对应的子串数量是 $len(i)-len(link(i))$,对自动机所有节点求和即可。
+另一种方法是利用上述后缀自动机的树形结构。每个节点对应的子串数量是 $len(i)-len(link(i))$ ,对自动机所有节点求和即可。
 
 ### 所有不同子串的总长度
 
@@ -375,7 +375,7 @@ $$
 
 算法的时间复杂度仍然是 $O(\left|S\right|)$ 。
 
-同样可以利用上述后缀自动机的树形结构。每个节点对应的所有后缀长度是 $\frac{len(i)\times (len(i)+1)}{2}$,减去其 $link$ 节点的对应值就是该节点的净贡献,对自动机所有节点求和即可。
+同样可以利用上述后缀自动机的树形结构。每个节点对应的所有后缀长度是 $\frac{len(i)\times (len(i)+1)}{2}$ ,减去其 $link$ 节点的对应值就是该节点的净贡献,对自动机所有节点求和即可。
 
 ### 字典序第 $k$ 大子串
 
@@ -401,7 +401,7 @@ $$
 
 > 对于一个给定的文本串 $T$ ,有多组询问,每组询问给一个模式串 $P$ ,回答模式串 $P$ 在字符串 $T$ 中作为子串出现了多少次。
 
-利用后缀自动机的树形结构,进行 dfs 即可预处理每个节点的终点集合大小。在自动机上查找模式串 $P$ 对应的节点,如果存在,则答案就是该节点的终点集合大小;如果不存在,则答案为 $0$.
+利用后缀自动机的树形结构,进行 dfs 即可预处理每个节点的终点集合大小。在自动机上查找模式串 $P$ 对应的节点,如果存在,则答案就是该节点的终点集合大小;如果不存在,则答案为 $0$ .
 
 以下为原方法:
 
@@ -458,21 +458,21 @@ $$
 利用后缀自动机的树形结构,遍历子树,一旦发现终点节点就输出。
 
 以下为原解法:
-> 
+
 > 我们还是对文本串 $T$ 构造后缀自动机。与上一个问题相似,我们为所有状态计算位置 $firstpos$ 。
-> 
+>
 > 如果 $t$ 为对应于模式串 $T$ 的状态,显然 $firstpos(t)$ 为答案的一部分。需要查找的其它位置怎么办?我们使用了含有字符串 $P$ 的自动机,我们还需要将哪些状态纳入自动机呢?所有对应于以 $P$ 为后缀的字符串的状态。换句话说我们要找到所有可以通过后缀链接到达状态 $t$ 的状态。
-> 
+>
 > 因此为了解决这个问题,我们需要为每一个状态保存一个指向它的后缀引用列表。查询的答案就包含了对于每个我们能从状态 $t$ 只使用后缀引用进行 DFS 或 BFS 的所有状态的 $firstpos$ 值。
-> 
+>
 > 这种变通方案的时间复杂度为 $O(answer(P))$ ,因为我们不会重复访问一个状态(因为对于仅有一个后缀链接指向一个状态,所以不存在两条不同的路径指向同一状态)。
-> 
+>
 > 我们只需要考虑两个可能有相同 $endpos$ 值的不同状态。如果一个状态是由另一个复制而来的,则这种情况会发生。然而,这并不会对复杂度分析造成影响,因为每个状态至多被复制一次。
-> 
+>
 > 此外,如果我们不从被复制的节点输出位置,我们也可以去除重复的位置。事实上对于一个状态,如果经过被复制状态可以到达,则经过原状态也可以到达。因此,如果我们给每个状态记录标记 `is_clone` 来代表这个状态是不是被复制出来的,我们就可以简单地忽略掉被复制的状态,只输出其它所有状态的 $firstpos$ 的值。
-> 
+>
 > 以下是大致的实现:
-> 
+>
 > ```cpp
 > struct state {
 >   bool is_clone;
@@ -480,10 +480,10 @@ $$
 >   std::vector<int> inv_link;
 >   // some other variables
 > };
-> 
+>
 > // 在构造 SAM 后
 > for (int v = 1; v < sz; v++) st[st[v].link].inv_link.push_back(v);
-> 
+>
 > // 输出所有出现位置
 > void output_all_occurrences(int v, int P_length) {
 >   if (!st[v].is_clone) cout << st[v].first_pos - P_length + 1 << endl;
@@ -530,7 +530,6 @@ $$
 
 -   如果仍然没有使用这一字符的转移,我们继续重复经过后缀链接并减小 $l$ ,直到我们找到一个转移或到达虚拟状态 $-1$ (这意味着字符 $T_{i}$ 根本没有在 $S$ 中出现过,所以我们设置 $v=l=0$ )。
 
-
 这一部分的时间复杂度为 $O(\left|T\right|)$ ,因为每次移动我们要么可以使 $l$ 增加一,要么可以在后缀链接间移动几次,每次都减小 $l$ 的值。
 
 代码实现: