### 额外信息
-观察 [实现](#_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)$ 对应字符串的所有后缀。
## 应用
总时间复杂度为: $O(\left|S\right|)$ 。
-另一种方法是利用上述后缀自动机的树形结构。每个节点对应的子串数量是 $len(i)-len(link(i))$,对自动机所有节点求和即可。
+另一种方法是利用上述后缀自动机的树形结构。每个节点对应的子串数量是 $len(i)-len(link(i))$ ,对自动机所有节点求和即可。
### 所有不同子串的总长度
算法的时间复杂度仍然是 $O(\left|S\right|)$ 。
-同样可以利用上述后缀自动机的树形结构。每个节点对应的所有后缀长度是 $\frac{len(i)\times (len(i)+1)}{2}$,减去其 $link$ 节点的对应值就是该节点的净贡献,对自动机所有节点求和即可。
+同样可以利用上述后缀自动机的树形结构。每个节点对应的所有后缀长度是 $\frac{len(i)\times (len(i)+1)}{2}$ ,减去其 $link$ 节点的对应值就是该节点的净贡献,对自动机所有节点求和即可。
### 字典序第 $k$ 大子串
> 对于一个给定的文本串 $T$ ,有多组询问,每组询问给一个模式串 $P$ ,回答模式串 $P$ 在字符串 $T$ 中作为子串出现了多少次。
-利用后缀自动机的树形结构,进行 dfs 即可预处理每个节点的终点集合大小。在自动机上查找模式串 $P$ 对应的节点,如果存在,则答案就是该节点的终点集合大小;如果不存在,则答案为 $0$.
+利用后缀自动机的树形结构,进行 dfs 即可预处理每个节点的终点集合大小。在自动机上查找模式串 $P$ 对应的节点,如果存在,则答案就是该节点的终点集合大小;如果不存在,则答案为 $0$ .
以下为原方法:
利用后缀自动机的树形结构,遍历子树,一旦发现终点节点就输出。
以下为原解法:
->
+
> 我们还是对文本串 $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;
> 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;
- 如果仍然没有使用这一字符的转移,我们继续重复经过后缀链接并减小 $l$ ,直到我们找到一个转移或到达虚拟状态 $-1$ (这意味着字符 $T_{i}$ 根本没有在 $S$ 中出现过,所以我们设置 $v=l=0$ )。
-
这一部分的时间复杂度为 $O(\left|T\right|)$ ,因为每次移动我们要么可以使 $l$ 增加一,要么可以在后缀链接间移动几次,每次都减小 $l$ 的值。
代码实现: