OSDN Git Service

Update sam.md
authorChro Ko <chrogeek@outlook.com>
Fri, 29 Mar 2019 09:48:53 +0000 (17:48 +0800)
committerChro Ko <chrogeek@outlook.com>
Fri, 29 Mar 2019 09:48:53 +0000 (17:48 +0800)
docs/string/sam.md

index 789a184..ef8302e 100644 (file)
@@ -1,37 +1,35 @@
- **后缀自动机** 是一个能解决许多字符串相关问题的有力的数据结构。
+ **后缀自动机** (suffix automaton, SAM) 是一个能解决许多字符串相关问题的有力的数据结构。
 
-举个例子,字符串问题:
+举个例子,以下的字符串问题都可以在线性时间内通过 SAM 解决。
 
 -   在另一个字符串中搜索一个字符串的所有出现位置。
 -   计算给定的字符串中有多少个不同的子串。
 
-以上问题都可以在线性的时间复杂度内通过后缀自动机来实现
+直观上,字符串的 SAM 可以理解为给定字符串的 **所有子串** 的压缩形式。值得注意的事实是, SAM 将所有的这些信息以高度压缩的形式储存。对于一个长度为 $n$ 的字符串,它的空间复杂度仅为 $O(n)$ 。此外,构造 SAM 的时间复杂度仅为 $O(n)$ (这里我们将字符集的大小 $k$ 看作常数,否则时间复杂度和空间复杂度均为 $O(n\log k)$ )
 
-直观上,字符串的后缀自动机可以理解为给定字符串的 **所有子串** 的压缩形式。值得注意的事实是,后缀自动机将所有的这些信息以高度压缩的形式储存。对于一个长度为 $n$ 的字符串,它的空间复杂度仅为 $O(n)$ 。此外,构造后缀自动机的时间复杂度仅为 $O(n)$ (这里我们将字符集的大小 $k$ 看作常数,否则时间复杂度和空间复杂度均为 $O(n\log k)$ )。
+## SAM 的定义
 
-## 后缀自动机的定义
-
-给定字符串 $s$ 的后缀自动机是一个接受所有字符串 $s$ 的后缀的最小 **DFA** (确定性有限自动机或确定性有限状态自动机)。
+字符串 $s$ 的 SAM 是一个接受 $s$ 的所有后缀的最小 **DFA** (确定性有限自动机或确定性有限状态自动机)。
 
 换句话说:
 
--   后缀自动机是一张有向无环图。顶点被称作 **状态** ,边被称作状态间的 **转移** 。
--   一个状态 $t_0$ 为 **初始状态** ,它必定为这张图的源点(其它各点均与 $t_0$ 连通)
--   每个 **转移** 都标有一些字母。从一个点出发的所有转移均 **不同** 。
--   一个或多个状态为 **终止状态** 。如果我们从初始状态 $t_0$ 出发,最终转移到了一个终止状态,则路径上的所有转移连接起来一定是字符串 $s$ 的一个后缀。 $s$ 的每个后缀均可用一条从 $t_0$ 到一个终止状态的路径构成。
--   å\90\8eç¼\80è\87ªå\8a¨æ\9cºæ\98¯æ\89\80æ\9c\89满足ä¸\8aè¿°æ\9d¡ä»¶ç\9a\84è\87ªå\8a¨æ\9cºä¸­é¡¶ç\82¹æ\95°æ\9c\80å°\91ç\9a\84ä¸\80个
+-    SAM 是一张有向无环图。结点被称作 **状态** ,边被称作状态间的 **转移** 。
+-   图存在一个源点 $t_0$ ,称作 **初始状态** ,其它各结点均可从 $t_0$ 出发到达
+-   每个 **转移** 都标有一些字母。从一个点出发的所有转移均 **不同** 。
+-   存在一个或多个 **终止状态** 。如果我们从初始状态 $t_0$ 出发,最终转移到了一个终止状态,则路径上的所有转移连接起来一定是字符串 $s$ 的一个后缀。 $s$ 的每个后缀均可用一条从 $t_0$ 到某个终止状态的路径构成。
+-   å\9c¨æ\89\80æ\9c\89满足ä¸\8aè¿°æ\9d¡ä»¶ç\9a\84è\87ªå\8a¨æ\9cºä¸­ï¼\8c SAM ç\9a\84ç»\93ç\82¹æ\95°æ\9c\80å°\91
 
 ### 子串的性质
 
-后缀自动机最简单和最重要的性质是,它包含关于字符串 $s$ 的所有子串的信息。任意从初始状态 $t_0$ 开始的路径,如果我们将转移路径上的标号写下来,都会形成 $s$ 的一个 **子串** 。反之每个 $s$ 的子串对应于从 $t_0$ 开始的某条路径。
+ SAM 最简单、也最重要的性质是,它包含关于字符串 $s$ 的所有子串的信息。任意从初始状态 $t_0$ 开始的路径,如果我们将转移路径上的标号写下来,都会形成 $s$ 的一个 **子串** 。反之每个 $s$ 的子串对应从 $t_0$ 开始的某条路径。
 
-为了简化表达,我们将会说子串 **对应于** 一条路径(从 $t_0$ 开始且一些标号构成这个子串)。反过来我们说任意一条路径 **对应于** 它的标号构成的字符串。
+为了简化表达,我们称子串 **对应** 一条路径(从 $t_0$ 开始、由一些标号构成这个子串)。反过来,我们说任意一条路径 **对应** 它的标号构成的字符串。
 
-一条或多条路径可以到达一个状态,因此我们说一个状态对应于字符串的集合,这也对应于那些路径。
+到达某个状态的路径可能不止一条,因此我们说一个状态对应一些字符串的集合,这个集合的每个元素对应这些路径。
 
-### 构造后缀自动机的实例
+### 构造 SAM
 
-我们将会在这里展示一些简单的字符串的后缀自动机
+我们将会在这里展示一些简单的字符串的 SAM 
 
 我们用蓝色表示初始状态,用绿色表示终止状态。
 
 
 ![](./images/SAM/SAabbb.svg)
 
-## 在线性时间内构造后缀自动机
+## 在线性时间内构造 SAM
 
-在我们描述线性时间内构造后缀自动机的算法之前,我们需要引入几个对理解构造过程非常重要的新概念并简单证明。
+在我们描述线性时间内构造 SAM 的算法之前,我们需要引入几个对理解构造过程非常重要的概念并简单证明。
 
-### 结束位置<script type="math/tex">endpos</script>
+### 结束位置 $endpos$
 
-<p>考虑字符串 <span><span class="MathJax_Preview">s</span><script type="math/tex">s</script></span> 的任意非空子串 <span><span class="MathJax_Preview">t</span><script type="math/tex">t</script></span> ,我们记 <span><span class="MathJax_Preview">endpos(t)</span><script type="math/tex">endpos(t)</script></span> 为在字符串 <span><span class="MathJax_Preview">s</span><script type="math/tex">s</script></span> 中 <span><span class="MathJax_Preview">t</span><script type="math/tex">t</script></span> 的所有结束位置(假设对字符串中字符的编号从零开始)。例如,对于字符串 <span><span class="MathJax_Preview">``abcbc\!"</span><script type="math/tex">``abcbc\!"</script></span>,我们有 <span><span class="MathJax_Preview">endpos(``bc\!")=2,\,4</span><script type="math/tex">endpos(``bc\!")=2,\,4</script></span>。</p>
+考虑字符串 $s$ 的任意非空子串 $t$ ,我们记 $endpos(t)$ 为在字符串 $s$ 中 $t$ 的所有结束位置(假设对字符串中字符的编号从零开始)。例如,对于字符串 $``abcbc\!"$,我们有 $endpos(``bc\!")=2,\,4$。
 
-当两个子串 $t_1$ 与 $t_2$ 的末尾集合相等时我们称它们是 $endpos$ 等价的:即 $endpos(t_1)=endpos(t_2)$ 。这样所有字符串 $s$ 的非空子串都可以根据它们的 ** $endpos$ ** 集合被分为几个 **等价类** 。
+两个子串 $t_1$ 与 $t_2$ 的 $endpos$ 集合可能相等: $endpos(t_1)=endpos(t_2)$ 。这样所有字符串 $s$ 的非空子串都可以根据它们的 $endpos$ 集合被分为若干 **等价类** 。
 
-显然,在后缀自动机中的每个状态对应于一个或多个 $endpos$ 相同的子串。换句话说,后缀自动机中的状态数等于所有子串的等价类的个数,加上初始状态。后缀自动机的状态个数等价于 $endpos$ 相同的一个或多个子串所组成的集合的个数 $+1$ 。
+显然, SAM 中的每个状态对应一个或多个 $endpos$ 相同的子串。换句话说, SAM 中的状态数等于所有子串的等价类的个数,再加上初始状态。 SAM 的状态个数等价于 $endpos$ 相同的一个或多个子串所组成的集合的个数 $+1$ 。
 
-我们稍后将会用这个假设介绍构造后缀自动机的算法。在那时我们将会发现,后缀自动机需要满足的所有性质,除了最小性以外都满足了。由 Nerode 定理我们可以得出最小性(这篇文章不会证明后缀自动机的最小性)。
+我们稍后将会用这个假设介绍构造 SAM 的算法。我们将发现, SAM 需要满足的所有性质,除了最小性以外都满足了。由 Nerode 定理我们可以得出最小性(不会在这篇文章中证明)。
 
 由 $endpos$ 的值我们可以得到一些重要结论:
 
->  **引理 1:** 当且仅当字符串 $u$ 以 $w$ 的一个后缀的形式出现在字符串 $s$ 中时,两个非空子串 $u$ 和 $w$ (假设 $length(u)\le length(w)$ )是 $endpos$ 等价的
+>  **引理 1:** 两个非空子串 $u$ 和 $w$ (假设 $length(u)\le length(w)$ )的 $endpos$ 相同,当且仅当字符串 $u$ 是 $w$ 的后缀
 
-引理显然成立。如果 $u$ 和 $w$ 的 $endpos$ 相同,则 $u$ 是 $w$ 的一个后缀,且只以 $s$ 中的一个 $w$ 的后缀的形式出现。且根据定义,如果 $u$ 为 $w$ 的一个后缀,且只以后缀的形式在 $s$ 中出现时,两个子串的 $endpos$ 值相等
+引理显然成立。如果 $u$ 和 $w$ 的 $endpos$ 相同,则 $u$ 是 $w$ 的一个后缀,且只以 $s$ 中的一个 $w$ 的后缀的形式出现。且根据定义,如果 $u$ 为 $w$ 的一个后缀,且只以后缀的形式在 $s$ 中出现时,两个子串的 $endpos$ 相同
 
->  **引理 2:** 考虑两个非空子串 $u$ 和 $w$ (假设 $length(u)\le length(w)$ )。则它们的 $endpos$ 构成的集合要么完全没有交集,要么 $endpos(w)$ 是 $endpos(u)$ 的一个子集。并且这依赖于 $u$ 是否为 $w$ 的一个后缀。即
+>  **引理 2:** 考虑两个非空子串 $u$ 和 $w$ (假设 $length(u)\le length(w)$ )。那么要么 $endpos(u)\cap endpos(w)=\varnothing$ ,要么 $endpos(w)\subseteq endpos(u)$ ,取决于 $u$ 是否为 $w$ 的一个后缀
 >
 > $$
 > \begin{cases}
 >  endpos(w)\subseteq endpos(u)&\text{若 $u$ 为 $w$ 的一个后缀}\\
->  endpos(w)\cap endpos(u)=\emptyset&\text{另一种情况}\\
+>  endpos(w)\cap endpos(u)=\varnothing&\text{另一种情况}\\
 > \end{cases}
 > $$
 
-证明:如果集合 $endpos(u)$ 与 $endpos(w)$ 有至少一个公共元素,那么由于字符串 $u$ 与 $w$ 都在一个位置结束,即 $u$ 是 $w$ 的一个后缀。但是如果如此在每次 $w$ 出现的位置子串 $u$ 也会出现,这意味着 $endpos(w)$ 是 $endpos(u)$ 的一个子集
+证明:如果集合 $endpos(u)$ 与 $endpos(w)$ 有至少一个公共元素,那么由于字符串 $u$ 与 $w$ 在相同位置结束, $u$ 是 $w$ 的一个后缀。所以在每次 $w$ 出现的位置,子串 $u$ 也会出现。所以 $endpos(w)\subseteq endpos(u)$ 
 
->  **引理 3:** 考虑一个 $endpos$ 等价类。将类中的所有子串按长度非递增的顺序排序。即每个子串都会比它前一个子串短,与此同时每个子串也是它前一个子串的一个后缀。换句话说,同一等价类中任两子串中较短者为较长者后缀,且该类中的子串长度恰好覆盖整个区间 $[x,\,y]$ 。
+>  **引理 3:** 考虑一个 $endpos$ 等价类,将类中的所有子串按长度非递增的顺序排序。每个子串都不会比它前一个子串长,与此同时每个子串也是它前一个子串的后缀。换句话说,对于同一等价类的任一两子串,较短者为较长者的后缀,且该等价类中的子串长度恰好覆盖整个区间 $[x,\,y]$ 。
 
-è¯\81æ\98\8eï¼\9aå\9bºå®\9aä¸\80äº\9b $endpos$ ç­\89ä»·ç±»ã\80\82å¦\82æ\9e\9c等价类中只包含一个子串,引理显然成立。现在我们来讨论子串元素个数大于 $1$ 的等价类。
+è¯\81æ\98\8eï¼\9aå¦\82æ\9e\9c $endpos$ 等价类中只包含一个子串,引理显然成立。现在我们来讨论子串元素个数大于 $1$ 的等价类。
 
-由引理 1,两个不同的 $endpos$ 等价字符串中较短的一个总是较长的一个的真后缀。因此,等价类中不可能有两个等长的字符串。
+由引理 1,两个不同的 $endpos$ 等价的字符串中,较短者总是较长者的真后缀。因此,等价类中没有等长的字符串。
 
-记 $w$ 为等价类中最长的字符串,类似地,记 $u$ 为等价类中最短的字符串。由引理 1,字符串 $u$ 是字符串 $w$ 的真后缀。现在考虑长度在区间 $[length(u),\,length(w)]$ 中的 $w$ 的任意后缀。容易看出,这个后缀也在同一等价类中。因为这个后缀只能在字符串 $s$ 中以 $w$ 的一个后缀的形式存在(也因为较短的后缀 $u$ 在 $s$ 中只以 $w$ 的后缀的形式存在)。因此,由引理 1,这个后缀与字符串 $w$  $endpos$ 等价
+记 $w$ 为等价类中最长的字符串、 $u$ 为等价类中最短的字符串。由引理 1,字符串 $u$ 是字符串 $w$ 的真后缀。现在考虑长度在区间 $[length(u),\,length(w)]$ 中的 $w$ 的任意后缀。容易看出,这个后缀也在同一等价类中,因为这个后缀只能在字符串 $s$ 中以 $w$ 的一个后缀的形式存在(也因为较短的后缀 $u$ 在 $s$ 中只以 $w$ 的后缀的形式存在)。因此,由引理 1,这个后缀和字符串 $w$ 的 $endpos$ 相同
 
-### 后缀链接<script type="math/tex">link</script>
+### 后缀链接 $link$
 
-考虑后缀自动机中满足 $v\ne t_0$ 的一些状态。我们已经知道,状态 $v$ 对应于具有相同 $endpos$ 的等价类。我们如果定义 $w$ 为这些字符串中最长的一个,则所有其它的字符串都是 $w$ 的后缀。
+考虑 SAM 中某个不是 $t_0$ 的状态 $v$。我们已经知道,状态 $v$ 对应于具有相同 $endpos$ 的等价类。我们如果定义 $w$ 为这些字符串中最长的一个,则所有其它的字符串都是 $w$ 的后缀。
 
-我们还知道字符串 $w$ 的前几个后缀(如果我们用长度降序考虑这些后缀)在这个等价类中全部被包含,且所有其它后缀(至少一个—空后缀)在其它的等价类中。我们记 $t$ 为最大的这样的后缀,然后用后缀链接连到 $t$ 上。
+我们还知道字符串 $w$ 的前几个后缀(按长度降序考虑)全部包含于这个等价类,且所有其它后缀(至少有一个——空后缀)在其它的等价类中。我们记 $t$ 为最长的这样的后缀,然后将 $v$ 的后缀链接连到 $t$ 上。
 
-换句话说,一个 **后缀链接**  $link(v)$ 连接到对应于 $w$ 的最长后缀的另一个 $endpos$ 等价类的状态。
+换句话说,一个 **后缀链接** $link(v)$ 连接到对应于 $w$ 的最长后缀的另一个 $endpos$ 等价类的状态。
 
-以下我们假设初始状态 $t_0$ 对应于它自己这个等价类(只包含一个空字符串),为了方便我们规定 $endpos(t)=\{-1,\,0,\,\ldots,\,length(s)-1\}$ 。
+以下我们假设初始状态 $t_0$ 对应于它自己这个等价类(只包含一个空字符串)。为了方便,我们规定 $endpos(t)=\{-1,\,0,\,\ldots,\,length(s)-1\}$ 。
 
 >  **引理 4:** 所有后缀链接构成一棵根节点为 $t_0$ 的树。
 
-证明:考虑任意满足 $v\ne t_0$ 的状态,一个后缀链接 $link(v)$ 连接到的状态对应于严格更短的字符串(根据后缀链接的定义和引理 3)。因此,通过在后缀链接上移动,我们早晚会到达对应空串的初始状态 $t_0$ 。
+证明:考虑任意不是 $t_0$ 的状态 $v$,后缀链接 $link(v)$ 连接到的状态对应于严格更短的字符串(后缀链接的定义、引理 3)。因此,沿后缀链接移动,我们总是能到达对应空串的初始状态 $t_0$ 。
 
->  **引理 5:** 如果我们使用集合 $endpos$ 构造一棵树(所有子节点的集合为父节点的子集),则这个结构由后缀链接连接起来
+>  **引理 5:** 通过 $endpos$ 集合构造的树(每个子节点的 $subset$ 都包含在父节点的 $subset$ 中)与通过后缀链接 $link$ 构造的树相同
 
 证明:由引理 2,我们可以用 $endpos$ 集合构造一棵树(因为两个集合要么完全没有交集要么其中一个是另一个的子集)。
 
-我们现在考虑任意满足 $v\ne t_0$ 的状态和它的后缀链接 $link(v)$ ,由后缀链接和引理 2,我们可以得到
+我们现在考虑任意不是 $t_0$ 的状态 $v$ 及后缀链接 $link(v)$ ,由后缀链接和引理 2,我们可以得到
 
 $$
-endpos(v)\subseteq endpos(link(v))
+endpos(v)\subseteq endpos(link(v)),
 $$
 
-,这与前面的引理证明了以下断言成立:后缀链接构成的树本质上是 $endpos$ 集合构成的一棵树。
+结合前面的引理有:后缀链接构成的树本质上是 $endpos$ 集合构成的一棵树。
 
-以下是对于字符串 $``abcbc\!"$ 构造后缀自动机时产生的后缀链接树的一个 **例子** ,节点被标记为对应等价类中最长的子串。
+以下是对字符串 $``abcbc\!"$ 构造 SAM 时产生的后缀链接树的一个 **例子** ,节点被标记为对应等价类中最长的子串。
 
 ![](./images/SAM/SA_suffix_links.svg)
 
 ### 小结
 
-在学习算法本身前,我们对之前学过的知识进行一下总结,并引入一些辅助记号。
+在学习算法本身前,我们总结一下之前学过的知识,并引入一些辅助记号。
 
 -    $s$ 的子串可以根据它们结束的位置 $endpos$ 被划分为多个等价类;
--   后缀自动机由初始状态 $t_0$ 和与每一个 $endpos$ 等价类对应的每个状态组成;
--   对于每一个状态 $v$ ,一个或多个子串与之匹配。我们记 $longest(v)$ 为其中最长的一个字符串,记 $len(v)$ 为它的长度。类似地,记 $shortest(v)$ 为最短的子串,它的长度为 $minlen(v)$ 。那么所有对应这个状态的所有字符串都是字符串 $longest(v)$ 的不同的后缀,且所有字符串的长度恰好覆盖区间 $[minlength(v),\,len(v)]$ 中的每一个整数。
--   对于任意满足 $v\ne t_0$ 的状态,定义后缀链接为连接到对应字符串 $longest(u)$ 的长度为 $minlen(v)-1$ 的后缀的一条边。从根节点 $t_0$ 出发的后缀链接可以形成一棵树,与此同时,这棵树形成了 $endpos$ 集合间的包含关系。
--   我们可以对 $v\ne t_0$ 的状态使用后缀链接 $link(v)$ 解释 $minlen(v)$ 如下
+-    SAM 由初始状态 $t_0$ 和与每一个 $endpos$ 等价类对应的每个状态组成;
+-   对于每一个状态 $v$ ,一个或多个子串与之匹配。我们记 $longest(v)$ 为其中最长的一个字符串,记 $len(v)$ 为它的长度。类似地,记 $shortest(v)$ 为最短的子串,它的长度为 $minlen(v)$ 。那么对应这个状态的所有字符串都是字符串 $longest(v)$ 的不同的后缀,且所有字符串的长度恰好覆盖区间 $[minlength(v),\,len(v)]$ 中的每一个整数。
+-   对于任意不是 $t_0$ 的状态 $v$ ,定义后缀链接为连接到对应字符串 $longest(u)$ 的长度为 $minlen(v)-1$ 的后缀的一条边。从根节点 $t_0$ 出发的后缀链接可以形成一棵树。这棵树也表示 $endpos$ 集合间的包含关系。
+-   对于 $t_0$ 以外的状态 $v$ ,可用后缀链接 $link(v)$ 表达 $minlen(v)$ 
 
 $$
 minlen(v)=len(link(v))+1.
 $$
 
--   å¦\82æ\9e\9cæ\88\91们ä»\8eä»»æ\84\8fç\8a¶æ\80\81 $v_0$ å¼\80å§\8b顺ç\9d\80å\90\8eç¼\80é\93¾æ\8e¥é\81\8då\8e\86ï¼\8cæ\97©æ\99\9aé\83½会到达初始状态 $t_0$ 。这种情况下我们可以得到一个互不相交的区间 $[minlen(v_i),\,len(v_i)]$ 的序列,且它们的并集形成了连续的区间 $[0,\,len(v_0)]$ 。
+-   å¦\82æ\9e\9cæ\88\91们ä»\8eä»»æ\84\8fç\8a¶æ\80\81 $v_0$ å¼\80å§\8b顺ç\9d\80å\90\8eç¼\80é\93¾æ\8e¥é\81\8då\8e\86ï¼\8cæ\80»会到达初始状态 $t_0$ 。这种情况下我们可以得到一个互不相交的区间 $[minlen(v_i),\,len(v_i)]$ 的序列,且它们的并集形成了连续的区间 $[0,\,len(v_0)]$ 。
 
 ### 算法
 
-现在我们可以学习算法本身了。这个算法是 **在线** 算法,这意味着我们可以逐个加入字符串中的每个字符,并且在每一步中对应地维护后缀自动机
+现在我们可以学习构造 SAM 的算法了。这个算法是 **在线** 算法,我们可以逐个加入字符串中的每个字符,并且在每一步中对应地维护 SAM 
 
-为了保证线性的空间复杂度,我们将只保存 $len$ 和 $link$ 的值和每个状态的一个转移列表,我们不会标记终止状态(但是我们稍后会展示在构造后缀自动机后如何分配这些标记)。
+为了保证线性的空间复杂度,我们将只保存 $len$ 和 $link$ 的值和每个状态的转移列表,我们不会标记终止状态(但是我们稍后会展示在构造 SAM 后如何分配这些标记)。
 
-一开始后缀自动机只包含一个状态 $t_0$ ,编号为 $0$ (其它状态的编号为 $1,\,2,\,\ldots$ )。为了方便,我们分配给它 $len=0$ 和 $link=-1$ ( $-1$ 表示一个虚拟的不存在的状态)。
+一开始 SAM 只包含一个状态 $t_0$ ,编号为 $0$ (其它状态的编号为 $1,\,2,\,\ldots$ )。为了方便,对于状态 $t_0$ 我们指定 $len=0$ 、 $link=-1$ ( $-1$ 表示虚拟状态)。
 
-现在整个任务转化为实现给当前字符串添加一个字符 $c$ 的过程。算法流程如下:
+现在任务转化为实现给当前字符串添加一个字符 $c$ 的过程。算法流程如下:
 
--   令 $last$ 为对应添加字符 $c$ 之前的整个字符串(一开始我们设置 $last=0$ 且我们会在算法的最后一步对应地更新 $last$ )。
+-   令 $last$ 为添加字符 $c$ 之前,整个字符串对应的状态(一开始我们设 $last=0$ ,算法的最后一步更新 $last$ )。
 -   创建一个新的状态 $cur$ ,并将 $len(cur)$ 赋值为 $len(last)+1$ ,在这时 $link(cur)$ 的值还未知。
--   ç\8e°å\9c¨æ\88\91们æ\8c\89以ä¸\8bæµ\81ç¨\8bè¿\9bè¡\8cï¼\9aæ\88\91们ä»\8eç\8a¶æ\80\81 $last$ å¼\80å§\8b。如果还没有到字符 $c$ 的转移,我们就添加一个到状态 $cur$ 的转移,遍历后缀链接。如果在某个点已经存在到字符 $c$ 的转移,我们就停下来,并将这个状态标记为 $p$ 。
+-   ç\8e°å\9c¨æ\88\91们æ\8c\89以ä¸\8bæµ\81ç¨\8bè¿\9bè¡\8cï¼\88ä»\8eç\8a¶æ\80\81 $last$ å¼\80å§\8bï¼\89。如果还没有到字符 $c$ 的转移,我们就添加一个到状态 $cur$ 的转移,遍历后缀链接。如果在某个点已经存在到字符 $c$ 的转移,我们就停下来,并将这个状态标记为 $p$ 。
 -   如果没有找到这样的状态 $p$ ,我们就到达了虚拟状态 $-1$ ,我们将 $link(cur)$ 赋值为 $0$ 并退出。
--   假设现在我们找到了一个状态 $p$ ,其可以转移到字符 $c$ ,我们将这个状态转移到的状态标记为 $q$ 。
+-   假设现在我们找到了一个状态 $p$ ,其可以通过字符 $c$ 转移。我们将转移到的状态标记为 $q$ 。
 -   现在我们分类讨论两种状态,要么 $len(p) + 1 = len(q)$ ,要么不是。
 -   如果 $len(p)+1=len(q)$ ,我们只要将 $link(cur)$ 赋值为 $q$ 并退出。
 -   否则就会有些复杂。需要 **复制** 状态 $q$ :我们创建一个新的状态 $clone$ ,复制 $q$ 的除了 $len$ 的值以外的所有信息(后缀链接和转移)。我们将 $len(clone)$ 赋值为 $len(p)+1$ 。  
@@ -166,33 +164,33 @@ $$
     最终我们需要使用后缀链接从状态 $p$ 返回,因为存在一条通过 $c$ 到状态 $q$ 的转移,并在此过程中重定向所有状态到状态 $clone$ 。
 -   以上三种情况,在完成这个过程之后,我们将 $last$ 的值更新为状态 $cur$ 。
 
-如果我们还想知道哪些状态是 **终止状态** 而哪些不是,我们可以在为字符串 $s$ 构造完完整的å\90\8eç¼\80è\87ªå\8a¨æ\9cºå\90\8eæ\89¾å\88°æ\89\80æ\9c\89ç\9a\84ç»\88æ­¢ç\8a¶æ\80\81ã\80\82为此ï¼\8cæ\88\91们ä»\8e对åº\94æ\95´ä¸ªå­\97符串ç\9a\84ç\8a¶æ\80\81ï¼\88å­\98å\82¨å\9c¨å\8f\98é\87\8f $last$ ä¸­ï¼\89ï¼\8cé\81\8då\8e\86å®\83ç\9a\84å\90\8eç¼\80é\93¾æ\8e¥ï¼\8cç\9b´å\88°å\88°è¾¾å\88\9då§\8bç\8a¶æ\80\81ã\80\82æ\88\91们å°\86æ\89\80æ\9c\89é\81\8då\8e\86å\88°ç\9a\84è\8a\82ç\82¹é\83½æ \87记为ç»\88æ­¢è\8a\82ç\82¹ã\80\82容æ\98\93ç\90\86解è¿\99æ ·å\81\9aæ\88\91们ä¼\9a精确å\9c°æ \87è®°å­\97符串 $s$ ç\9a\84æ\89\80æ\9c\89å\90\8eç¼\80ï¼\8cè¿\99äº\9bç\8a¶æ\80\81æ\81°å¥½æ\98¯ç»\88æ­¢ç\8a¶æ\80\81ã\80\82
+如果我们还想知道哪些状态是 **终止状态** 而哪些不是,我们可以在为字符串 $s$ 构造完完整的 SAM å\90\8eæ\89¾å\88°æ\89\80æ\9c\89ç\9a\84ç»\88æ­¢ç\8a¶æ\80\81ã\80\82为此ï¼\8cæ\88\91们ä»\8e对åº\94æ\95´ä¸ªå­\97符串ç\9a\84ç\8a¶æ\80\81ï¼\88å­\98å\82¨å\9c¨å\8f\98é\87\8f $last$ ä¸­ï¼\89ï¼\8cé\81\8då\8e\86å®\83ç\9a\84å\90\8eç¼\80é\93¾æ\8e¥ï¼\8cç\9b´å\88°å\88°è¾¾å\88\9då§\8bç\8a¶æ\80\81ã\80\82æ\88\91们å°\86æ\89\80æ\9c\89é\81\8då\8e\86å\88°ç\9a\84è\8a\82ç\82¹é\83½æ \87记为ç»\88æ­¢è\8a\82ç\82¹ã\80\82容æ\98\93ç\90\86解è¿\99æ ·å\81\9aæ\88\91们ä¼\9aå\87\86ç¡®å\9c°æ \87è®°å­\97符串 $s$ ç\9a\84æ\89\80æ\9c\89å\90\8eç¼\80ï¼\8cè¿\99äº\9bç\8a¶æ\80\81é\83½æ\98¯ç»\88æ­¢ç\8a¶æ\80\81ã\80\82
 
-在下一部分,我们将观察算法每一步的细节,并证明它的 **正确性** 。
+在下一部分,我们将检查算法每一步的细节,并证明它的 **正确性** 。
 
-现在,我们只注意到,因为我们只为 $s$ 的每个字符创建一个或两个新状态所以后缀自动机只包含 **线性个** 状态。
+因为我们只为 $s$ 的每个字符创建一个或两个新状态,所以 SAM 只包含 **线性个** 状态。
 
½¬ç§»ä¸ªæ\95°æ\98¯çº¿æ\80§è§\84模ç\9a\84ï¼\8c以å\8f\8aæ\80»ä½\93ä¸\8aç®\97æ³\95ç\9a\84è¿\90è¡\8cæ\97¶é\97´æ\98¯çº¿æ\80§è§\84模ç\9a\84ï¼\8cè¿\99两ç\82¹还不那么清楚。
\80\8c线æ\80§è§\84模ç\9a\84转移个æ\95°ï¼\8c以å\8f\8aç®\97æ³\95æ\80»ä½\93ç\9a\84线æ\80§è¿\90è¡\8cæ\97¶é\97´还不那么清楚。
 
 ### 正确性证明
 
--   若一个转移 $(p,\,q)$ 满足 $len(p)+1=len(q)$ 则我们称这个转移是 **连续的** 。否则,即当 $len(p)+1<len(q)$ 时,这个转移被称为 **不连续的** 。从算法描述中可以看出,连续的和非连续的转移是算法的不同情况。连续的转移是固定的,我们不会再改变了。与此相反,当向字符串中插入一个新的字符时,非连续的转移可能会改变(转移边的端点可能会改变)。
--   为了避免引起歧义,我们记向后缀自动机中插入当前字符 $c$ 之前的字符串为 $s$ 。
+-   若一个转移 $(p,\,q)$ 满足 $len(p)+1=len(q)$ ,则我们称这个转移是 **连续的** 。否则,即当 $len(p)+1<len(q)$ 时,这个转移被称为 **不连续的** 。从算法描述中可以看出,连续的、不连续的转移是算法的不同情况。连续的转移是固定的,我们不会再改变了。与此相反,当向字符串中插入一个新的字符时,不连续的转移可能会改变(转移边的端点可能会改变)。
+-   为了避免引起歧义,我们记向 SAM 中插入当前字符 $c$ 之前的字符串为 $s$ 。
 -   算法从创建一个新状态 $cur$ 开始,对应于整个字符串 $s+c$ 。我们创建一个新的节点的原因很清楚。与此同时我们也创建了一个新的字符和一个新的等价类。
--   在创建一个新的状态之后,我们会从对应于整个字符串 $s$ 的状态通过后缀链接进行遍历。对于每一个状态,我们尝试添加一个从字符 $c$ 到新状态 $cur$ 的转移。然而我我们只能添加与原来已存在的转移不冲突的转移。因此我们只要找到已存在的 $c$ 的转移,我们就必须停止。
+-   在创建一个新的状态之后,我们会从对应整个字符串 $s$ 的状态通过后缀链接进行遍历。对于每一个状态,我们尝试添加一个通过字符 $c$ 到新状态 $cur$ 的转移。然而我们只能添加与原有转移不冲突的转移。因此我们只要找到已存在的 $c$ 的转移,我们就必须停止。
 -   最简单的情况是我们到达了虚拟状态 $-1$ ,这意味着我们为所有 $s$ 的后缀添加了 $c$ 的转移。这也意味着,字符 $c$ 从未在字符串 $s$ 中出现过。因此 $cur$ 的后缀链接为状态 $0$ 。
--   第二种情况下,我们找到了现有的转移 $(p,\,q)$ 。这意味着我们尝试向自动机内添加一个 **已经存在的** 字符串 $x+c$ (其中 $x$ 为 $s$ 的一个后缀,且字符串 $x+c$ 已经作为 $s$ 的一个子串出现过了)。因为我们假设字符串 $s$ 的自动机的构造是正确的,我们不应该在这里添加一个新的转移。然而,有一个难点。从状态 $cur$ 出发的后缀链接应该连接到哪个状态呢?我们要把后缀链接连到一个状态上,且其中最长的一个字符串恰好是 $x+c$ ,即这个状态的 $len$ 应该是 $len(p)+1$ 。然而还不存在这样的状态,即 $len(q)>len(p)+1$ 。这种情况下,我们必须要通过拆开状态 $q$ 来创建一个这样的状态。
+-   第二种情况下,我们找到了现有的转移 $(p,\,q)$ 。这意味着我们尝试向自动机内添加一个 **已经存在的** 字符串 $x+c$ (其中 $x$ 为 $s$ 的一个后缀,且字符串 $x+c$ 已经作为 $s$ 的一个子串出现过了)。因为我们假设字符串 $s$ 的自动机的构造是正确的,我们不应该在这里添加一个新的转移。然而,难点在于,从状态 $cur$ 出发的后缀链接应该连接到哪个状态呢?我们要把后缀链接连到一个状态上,且其中最长的一个字符串恰好是 $x+c$ ,即这个状态的 $len$ 应该是 $len(p)+1$ 。然而还不存在这样的状态,即 $len(q)>len(p)+1$ 。这种情况下,我们必须通过拆开状态 $q$ 来创建一个这样的状态。
 -   如果转移 $(p,\,q)$ 是连续的,那么 $len(q)=len(p)+1$ 。在这种情况下一切都很简单。我们只需要将 $cur$ 的后缀链接指向状态 $q$ 。
 -   否则转移是不连续的,即 $len(q)>len(p)+1$ ,这意味着状态 $q$ 不只对应于长度为 $len(p)+1$ 的后缀 $s+c$ ,还对应于 $s$ 的更长的子串。除了将状态 $q$ 拆成两个子状态以外我们别无他法,所以第一个子状态的长度就是 $len(p)+1$ 了。  
     我们如何拆开一个状态呢?我们 **复制** 状态 $q$ ,产生一个状态 $clone$ ,我们将 $len(clone)$ 赋值为 $len(p)+1$ 。由于我们不想改变遍历到 $q$ 的路径,我们将 $q$ 的所有转移复制到 $clone$ 。我们也将从 $clone$ 出发的后缀链接设置为 $q$ 的后缀链接的目标,并设置 $q$ 的后缀链接为 $clone$ 。  
     在拆开状态后,我们将从 $cur$ 出发的后缀链接设置为 $clone$ 。  
-    最后一步我们将一些到 $q$ 转移重定向到 $clone$ 。我们需要修改哪些转移呢?只重定向相当于所有字符串 $w+c$ (其中 $w$ 是 $p$ 的最长字符串)的后缀就够了。即,我们需要继续沿着后缀链接遍历,从点 $p$ 直到虚拟状态 $-1$ 或者是转移到不是状态 $q$ 的一个转移。
+    最后一步我们将一些到 $q$ 转移重定向到 $clone$ 。我们需要修改哪些转移呢?只重定向相当于所有字符串 $w+c$ (其中 $w$ 是 $p$ 的最长字符串)的后缀就够了。即,我们需要继续沿着后缀链接遍历,从点 $p$ 直到虚拟状态 $-1$ 或者是转移到不是状态 $q$ 的一个转移。
 
 ### 对操作次数为线性的证明
 
-首先我们假设字符集大小为 **常数** 。如果字符集大小不是常数,后缀自动机的时间复杂度就不是线性的。从一个顶点出发的转移存储在支持快速查询和插入的平衡树中。因此如果我们记 $k$ 为字符集的大小,则算法的渐进时间复杂度为 $O(n\log k)$ ,空间复杂度为 $O(n)$ 。然而如果字符集足够小,可以不写平衡树,以空间换时间将每个顶点的转移存储为长度为 $k$ 的数组(用于快速查询)和链表(用于快速遍历所有可用关键字)。这样算法的时间复杂度为 $O(n)$ ,空间复杂度为 $O(nk)$ 。
+首先我们假设字符集大小为 **常数** 。如果字符集大小不是常数,SAM 的时间复杂度就不是线性的。从一个结点出发的转移存储在支持快速查询和插入的平衡树中。因此如果我们记 $k$ 为字符集的大小,则算法的渐进时间复杂度为 $O(n\log k)$ ,空间复杂度为 $O(n)$ 。然而如果字符集足够小,可以不写平衡树,以空间换时间将每个结点的转移存储为长度为 $k$ 的数组(用于快速查询)和链表(用于快速遍历所有可用关键字)。这样算法的时间复杂度为 $O(n)$ ,空间复杂度为 $O(nk)$ 。
 
-所以我们将认为字符集的大小为常数,即每次对一个字符搜索转移、添加转移、查找下一个转移这些操作的时间复杂度都为 $O(1)$ 。
+所以我们将认为字符集的大小为常数,即每次对一个字符搜索转移、添加转移、查找下一个转移这些操作的时间复杂度都为 $O(1)$ 。
 
 如果我们考虑算法的各个部分,算法中有三处时间复杂度不明显是线性的:
 
@@ -200,17 +198,17 @@ $$
 -   第二处是当状态 $q$ 被复制到一个新的状态 $clone$ 时复制转移的过程。
 -   第三处是修改指向 $q$ 的转移,将它们重定向到 $clone$ 的过程。
 
-我们使用后缀自动机的大小(状态数和转移数)为 **线性的** 的事实(对状态数是线性的的证明就是算法本身,对转移数为线性的的证明将在稍后实现算法后给出)。
+我们使用 SAM 的大小(状态数和转移数)为 **线性的** 的事实(对状态数是线性的的证明就是算法本身,对转移数为线性的的证明将在稍后实现算法后给出)。
 
 因此上述 **第一处和第二处** 的总复杂度显然为线性的,因为单次操作均摊只为自动机添加了一个新转移。
 
-还需为 **第三处** 估计总复杂度,我们将最初指向 $q$ 的转移重定向到 $clone$ 。我们记 $v=longest(p)$ ,这是一个字符串 $s$ 的后缀,每次迭代长度都递减—因为作为字符串 $s$ 的位置随着每次迭代都单调上升。这种情况下,如果在循环的第一次迭代之前,相对应的字符串 $v$ 在距离 $last$ 的深度为 $k$  $(k\ge2)$ 的位置上(深度记为后缀链接的数量),那么在最后一次迭代后,字符串 $v+c$ 将会成为路径上第二个从 $cur$ 出发的后缀链接(它将会成为新的 $last$ 的值)。
+还需为 **第三处** 估计总复杂度,我们将最初指向 $q$ 的转移重定向到 $clone$ 。我们记 $v=longest(p)$ ,这是一个字符串 $s$ 的后缀,每次迭代长度都递减——因为字符串 $s$ 的位置每次迭代都单调上升。这种情况下,如果在循环的第一次迭代之前,相对应的字符串 $v$ 在距离 $last$ 的深度为 $k$  $(k\ge 2)$ 的位置上(深度记为后缀链接的数量),那么在最后一次迭代后,字符串 $v+c$ 将会成为路径上第二个从 $cur$ 出发的后缀链接(它将会成为新的 $last$ 的值)。
 
 因此,循环中的每次迭代都会使作为当前字符串的后缀的字符串 $longest(link(link(last))$ 的位置单调递增。因此这个循环最多不会执行超过 $n$ 次迭代,这正是我们需要证明的。
 
 ### 实现
 
-首先,我们描述一种存储一个转移的全部信息的数据结构。如果需要的话,你可以在这里加入一个终止标记,也可以是一些其它信息。我们将会用一个 `map` 存储转移的列表,允许我们在总计 $O(n)$ 的空间复杂度和 $O(n\log k)$ 的时间复杂度内处理整个字符串。
+首先,我们实现一种存储一个转移的全部信息的数据结构。如果需要的话,你可以在这里加入一个终止标记,也可以是一些其它信息。我们将用一个 `map` 存储转移的列表,允许我们在总计 $O(n)$ 的空间复杂度和 $O(n\log k)$ 的时间复杂度内处理整个字符串。
 
 ```cpp
 struct state {
@@ -219,7 +217,7 @@ struct state {
 };
 ```
 
-后缀自动机本身将会存储在一个 `state` 结构体数组中。我们记录当前自动机的大小 `sz` 和变量 `last` ,当前整个字符串对应的状态。
+SAM 本身将会存储在一个 `state` 结构体数组中。我们记录当前自动机的大小 `sz` 和变量 `last` ,当前整个字符串对应的状态。
 
 ```cpp
 const int MAXLEN = 100000;
@@ -227,7 +225,7 @@ state st[MAXLEN * 2];
 int sz, last;
 ```
 
-我们定义一个函数来初始化后缀自动机(创建一个只有一个状态的后缀自动机)。
+我们定义一个函数来初始化 SAM (创建一个只有一个状态的 SAM)。
 
 ```cpp
 void sa_init() {
@@ -238,7 +236,7 @@ void sa_init() {
 }
 ```
 
-最终我们给出主函数的实现给当前行末增加一个字符,对应地重建自动机。
+最终我们给出主函数的实现给当前行末增加一个字符,对应地重建自动机。
 
 ```cpp
 void sa_extend(char c) {
@@ -271,69 +269,53 @@ void sa_extend(char c) {
 }
 ```
 
-正如之前提到的一样,如果你用内存换时间(空间复杂度为 $O(nk)$ ,其中 $k$ 为字符集大小),你可以在 $O(n)$ 的时间内构造字符集大小 $k$ 任意的后缀自动机。但是这样你需要为每一个状态储存一个大小为 $k$ 的数组(用于快速跳转到转移的字符),和另外一个所有转移的链表(用于快速在转移中迭代)。
+正如之前提到的一样,如果你用内存换时间(空间复杂度为 $O(nk)$ ,其中 $k$ 为字符集大小),你可以在 $O(n)$ 的时间内构造字符集大小 $k$ 任意的 SAM。但是这样你需要为每一个状态储存一个大小为 $k$ 的数组(用于快速跳转到转移的字符),和另外一个所有转移的链表(用于快速在转移中迭代)。
 
-## 更多性质
+## 更多性质
 
 ### 状态数
 
-对于一个长度为 $n$ 的字符串 $s$ ,它的后缀自动机中的状态数 **不会超过 $2n-1$ ** (假设 $n\ge2$ )。
-
-对上述结论的证明就是算法本身,因为一开始自动机含有一个状态,第一次和第二次迭代中只会创建一个节点,剩余的 $n-2$ 步中每步会创建至多 $2$ 个状态。
-
-然而我们也能在 **不知道这个算法** 的情况下 **展示** 这个估计值。我们回忆一下状态数等于不同的 $endpos$ 集合个数。另外这些 $endpos$ 集合形成了一棵树(祖先节点的集合包含了它所有孩子节点的集合)。考虑将这棵树稍微变形一下:只要它有一个只有一个孩子的内部顶点(这意味着该子节点的集合至少遗漏了它的父集合中的一个位置),我们创建一个含有这个遗漏位置的集合。最后我们可以获得一棵每一个内部顶点的度数大于一的树,并且叶子节点的个数不超过 $n$ 。因此这样的树里有不超过 $2n-1$ 个节点。
+对于一个长度为 $n$ 的字符串 $s$ ,它的 SAM 中的状态数 **不会超过** $2n-1$ (假设 $n\ge2$ )。
 
-对于每个确定的 $n$ ,状态数的上界是确定的。一个可能的字符串是:
+算法本身即可证明该结论。一开始,自动机含有一个状态,第一次和第二次迭代中只会创建一个节点,剩余的 $n-2$ 步中每步会创建至多 $2$ 个状态。
 
-$$
-``abbb\ldots bbb\!"
-$$
+然而我们也能在 **不借助这个算法** 的情况下 **证明** 这个估计值。我们回忆一下状态数等于不同的 $endpos$ 集合个数。这些 $endpos$ 集合形成了一棵树(祖先节点的集合包含了它所有孩子节点的集合)。考虑将这棵树稍微变形一下:只要它有一个只有一个孩子的内部结点(这意味着该子节点的集合至少遗漏了它的父集合中的一个位置),我们创建一个含有这个遗漏位置的集合。最后我们可以获得一棵每一个内部结点的度数大于 1 的树,且叶子节点的个数不超过 $n$ 。因此这样的树里有不超过 $2n-1$ 个节点。
 
-从第三次迭代后的每次迭代,算法都会拆开一个状态,最终产生恰好 $2n-1$ 个状态。
+字符串 $``abbb\ldots bbb\!"$ 的状态数达到了该上界:从第三次迭代后的每次迭代,算法都会拆开一个状态,最终产生恰好 $2n-1$ 个状态。
 
 ### 转移数
 
-对于一个长度为 $n$ 的字符串 $s$ ,它的后缀自动机中的转移数 **不会超过 $3n-4$ ** (假设 $n\ge 3$ )。
+对于一个长度为 $n$ 的字符串 $s$ ,它的 SAM 中的转移数 **不会超过** $3n-4$ (假设 $n\ge 3$ )。
 
 证明如下:
 
-我们首先估计连续的转移的数量。考虑自动机中从状态 $t_0$ 开始的最长路径的生成树。生成树的骨架只包含连续的边,因此数量少于状态数,即,边数不会超过 $2n-2$ 。
-
-现在我们来估计非连续的转移的数量。令当前非连续转移为 $(p,\,q)$ ,其字符为 $c$ 。我们取它的对应字符串 $u+c+w$ ,其中字符串 $u$ 对应于初始状态到 $p$ 的最长路径, $w$ 对应于从 $p$ 到任意终止状态的最长路径。一方面,对于每个不完整的字符串所对应的形如 $u+c+w$ 的字符串是不同的(因为字符串 $u$ 和 $w$ 仅由完整的转移组成)。另一方面,由终止状态的定义,每个形如 $u+c+w$ 的字符串都是整个字符串 $s$ 的后缀。因为 $s$ 只有 $n$ 个非空后缀,且形如 $u+c+w$ 的字符串都不包含 $s$ (因为整个字符串只包含完整的转移),所以非完整的转移的总数不会超过 $n-1$ 。
+我们首先估计连续的转移的数量。考虑自动机中从状态 $t_0$ 开始的所有最长路径的生成树。生成树只包含连续的边,因此数量少于状态数,即边数不会超过 $2n-2$ 。
 
-将以上两个估计值结合起来,我们可以得到上界 $3n-3$ 。然而,最大的状态数只能在测试数据 $``abbb\ldots bbb\!"$ 中产生,这个测试数据的转移数量显然少于 $3n-3$ ,我们可以获得更为紧确的后缀自动机的转移数的上界: $3n-4$ 。
+现在我们来估计不连续的转移的数量。令当前不连续转移为 $(p,\,q)$ ,其字符为 $c$ 。我们取它的对应字符串 $u+c+w$ ,其中字符串 $u$ 对应于初始状态到 $p$ 的最长路径, $w$ 对应于从 $p$ 到任意终止状态的最长路径。一方面,每个不完整的字符串所对应的形如 $u+c+w$ 的字符串是不同的(因为字符串 $u$ 和 $w$ 仅由完整的转移组成)。另一方面,由终止状态的定义,每个形如 $u+c+w$ 的字符串都是整个字符串 $s$ 的后缀。因为 $s$ 只有 $n$ 个非空后缀,且形如 $u+c+w$ 的字符串都不包含 $s$ (因为整个字符串只包含完整的转移),所以非完整的转移的总数不会超过 $n-1$ 。
 
-上界可以通过字符串
-
-$$
-``abbb\ldots bbbc\!"
-$$
-
-达到。
+将以上两个估计值相加,我们可以得到上界 $3n-3$ 。然而,最大的状态数只能在类似于 $``abbb\ldots bbb\!"$ 的情况中产生,而此时转移数量显然少于 $3n-3$ 。因此我们可以获得更为紧确的 SAM 的转移数的上界: $3n-4$ 。字符串 $``abbb\ldots bbbc\!"$ 就达到了这个上界。
 
 ## 应用
 
-ä¸\8bé\9d¢æ\88\91们æ\9d¥ç\9c\8bä¸\80ä¸\8bä¸\80äº\9bå\8f¯ä»¥ç\94¨å\90\8eç¼\80è\87ªå\8a¨æ\9cºè§£å\86³ç\9a\84é\97®é¢\98ã\80\82为äº\86ç®\80å\8d\95ï¼\8cæ\88\91们å\81\87设å­\97符é\9b\86ç\9a\84大å°\8f $k$ ä¸ºå¸¸æ\95°ï¼\8c允许我们认为增加一个字符和遍历的复杂度为常数。
+ä¸\8bé\9d¢æ\88\91们æ\9d¥ç\9c\8bä¸\80äº\9bå\8f¯ä»¥ç\94¨ SAM è§£å\86³ç\9a\84é\97®é¢\98ã\80\82ç®\80å\8d\95èµ·è§\81ï¼\8cå\81\87设å­\97符é\9b\86ç\9a\84大å°\8f $k$ ä¸ºå¸¸æ\95°ã\80\82è¿\99允许我们认为增加一个字符和遍历的复杂度为常数。
 
 ### 检查字符串是否出现
 
 > 给一个文本串 $T$ 和多个模式串 $P$ ,我们要检查字符串 $P$ 是否作为 $T$ 的一个子串出现。
 
-我们在 $O(length(T))$ 的时间内为文本串 $T$ 构造后缀自动机。为了检查模式串 $P$ 是否在 $T$ 中出现,我们沿转移(边)从 $t_0$ 开始根据 $P$ 的字符进行转移。如果在某个点无法转移下去,则模式串 $P$ 不是 $T$ 的一个子串。如果我们能够这样处理完整个字符串 $P$ ,那么模式串在 $T$ 中出现过。因此
+我们在 $O(length(T))$ 的时间内为文本串 $T$ 构造 SAM。为了检查模式串 $P$ 是否在 $T$ 中出现,我们沿转移(边)从 $t_0$ 开始根据 $P$ 的字符进行转移。如果在某个点无法转移下去,则模式串 $P$ 不是 $T$ 的一个子串。如果我们能够这样处理完整个字符串 $P$ ,那么模式串在 $T$ 中出现过。
 
-对于每个字符串 $P$ 算法的时间复杂度为 $O(length(P))$ 。此外,这个算法还找到了模式串 $P$ 在文本串中出现的最大前缀长度。
+对于每个字符串 $P$ 算法的时间复杂度为 $O(length(P))$ 。此外,这个算法还找到了模式串 $P$ 在文本串中出现的最大前缀长度。
 
 ### 不同子串个数
 
 > 给一个字符串 $S$ ,计算不同子串的个数。
 
-为字符串 $S$ 构造后缀自动机
+为字符串 $S$ 构造 SAM 
 
 每个 $S$ 的子串都相当于自动机中的一些路径。因此不同子串的个数等于自动机中以 $t_0$ 为起点的不同路径的条数。
 
-考虑到后缀自动机为有向无环图,不同路径的条数可以使用动态规划计算。
-
-即,令 $d[v]$ 为从状态 $v$ 开始的路径数量(包括长度为零的路径),则我们有如下递推方程式:
+考虑到 SAM 为有向无环图,不同路径的条数可以通过动态规划计算。即令 $d[v]$ 为从状态 $v$ 开始的路径数量(包括长度为零的路径),则我们有如下递推方程:
 
 $$
 d[v]=1+\sum_{w:(v,\,w,\,c)\in DAWG}d[w]
@@ -351,21 +333,21 @@ $$
 
 本题做法与上一题类似,只是现在我们需要考虑分两部分进行动态规划:不同子串的数量 $d[v]$ 和它们的总长度 $ans[v]$ 。
 
-我们已经在上一题中介绍了如何计算 $d[v]$ 。 $ans[v]$ 的值可以使用通过以下递推式计算:
+我们已经在上一题中介绍了如何计算 $d[v]$ 。 $ans[v]$ 的值可以通过以下递推式计算:
 
 $$
 ans[v]=\sum_{w:(v,\,w,\,c)\in DAWG}d[w]+ans[w]
 $$
 
-我们取每个邻接点 $w$ 的答案,并加上 $d[w]$ (因为从状态 $v$ 出发的子串都增加了一个字符)。
+我们取每个邻接点 $w$ 的答案,并加上 $d[w]$ (因为从状态 $v$ 出发的子串都增加了一个字符)。
 
 算法的时间复杂度仍然是 $O(length(S))$ 。
 
-### 字典序第<script type="math/tex">k</script>大子串
+### 字典序第 $k$ 大子串
 
-> 给定一个字符串 $S$ 。多组询问,每组询问给定一个数 $K_i$ ,查询所有子串中字典序第 $k$ 大的子串。
+> 给定一个字符串 $S$ 。多组询问,每组询问给定一个数 $K_i$ ,查询所有子串中字典序第 $K_i$ 大的子串。
 
-解决这个问题的思路基于前两个问题的思路。字典序第 $k$ 大的子串对应于后缀自动机中字典序第 $k$ 大的路径。因此在计算每个状态的路径数后,我们可以很容易地从后缀自动机的根开始找到第 $k$ 大的路径。
+解决这个问题的思路基于前两个问题的思路。字典序第 $k$ 大的子串对应于 SAM 中字典序第 $k$ 大的路径。因此在计算每个状态的路径数后,我们可以很容易地从 SAM 的根开始找到第 $k$ 大的路径。
 
 预处理的时间复杂度为 $O(length(S))$ ,单次查询的复杂度为 $O(length(ans)\cdot k)$ (其中 $ans$ 是查询的答案, $k$ 为字符集的大小)。
 
@@ -373,7 +355,7 @@ $$
 
 > 给定一个字符串 $S$ 。找出字典序最小的循环移位。
 
-我们为字符串 $S+S$ 构造后缀自动机。则后缀自动机本身将包含字符串 $S$ 的所有循环移位作为路径。
+我们为字符串 $S+S$ 构造 SAM 。则 SAM 本身将包含字符串 $S$ 的所有循环移位作为路径。
 
 所以问题简化为寻找最小的长度为 $length(S)$ 的路径,这可以通过平凡的方法做到:我们从初始状态开始,贪心地访问最小的字符即可。
 
@@ -383,13 +365,13 @@ $$
 
 > 对于一个给定的文本串 $T$ ,有多组询问,每组询问给一个模式串 $P$ ,回答模式串 $P$ 在字符串 $T$ 中作为子串出现了多少次。
 
-我们为文本串 $T$ 构造后缀自动机
+对文本串 $T$ 构造 SAM
 
-接下来我们做以下的预处理:对于自动机中的每个状态 $v$ ,预处理值等于 $endpos(v)$ 这个集合大小的 $cnt[v]$ 。事实上对应于同一状态 $v$ 的所有子串在文本串 $T$ 中的出现次数相同,这相当于集合 $endpos$ 中的位置数。
+接下来做预处理:对于自动机中的每个状态 $v$ ,预处理 $cnt[v]$ ,使之等于 $endpos(v)$ 集合的大小。事实上,对应同一状态 $v$ 的所有子串在文本串 $T$ 中的出现次数相同,这相当于集合 $endpos$ 中的位置数。
 
 然而我们不能明确的构造集合 $endpos$ ,因此我们只考虑它们的大小 $cnt$ 。
 
-为了计算这些值,我们进行以下操作。对于每个状态,如果它不是通过复制创建的(且它不是初始状态 $t_0$ ),我们用 $cnt=1$ 初始化它。然后我们按它们的长度 $len$ 降序遍历所有状态,并将当前的 $cnt[v]$ 的值加到后缀链接上,即:
+为了计算这些值,我们进行以下操作。对于每个状态,如果它不是通过复制创建的(且它不是初始状态 $t_0$ ),我们将它的 $cnt$ 初始化为 1。然后我们按它们的长度 $len$ 降序遍历所有状态,并将当前的 $cnt[v]$ 的值加到后缀链接上,即:
 
 $$
 cnt[link(v)]+=cnt[v]
@@ -405,13 +387,13 @@ $$
 
 因此,我们可以在 $O(length(T))$ 的时间内计算出所有状态的 $cnt$ 的值。
 
-最后回答询问只需要查找查找值 $cnt[t]$ ,其中 $t$ 为如果存在这样的状态就是状态对应的模式串,如果不存在答案就为 $0$ 。单次查询的时间复杂度为 $O(length(P))$ 。
+最后回答询问只需要查找值 $cnt[t]$ ,其中 $t$ 为模式串对应的状态,如果该模式串不存在答案就为 $0$ 。单次查询的时间复杂度为 $O(length(P))$ 。
 
 ### 第一次出现的位置
 
 > 给定一个文本串 $T$ ,多组查询。每次查询字符串 $P$ 在字符串 $T$ 中第一次出现的位置( $P$ 的开头位置)。
 
-我们再构造一个后缀自动机。我们对自动机中的所有状态预处理位置 $firstpos$ 。即,对每个状态 $v$ 我们想要找到第一次出现这个状态的末端的位置 $firstpos[v]$ 。换句话说,我们希望先找到每个集合 $endpos$ 中的最小的元素(显然我们不能显式地维护所有 $endpos$ 集合)。
+我们构造一个 SAM。我们对自动机中的所有状态预处理位置 $firstpos$ 。即,对每个状态 $v$ 我们想要找到第一次出现这个状态的末端的位置 $firstpos[v]$ 。换句话说,我们希望先找到每个集合 $endpos$ 中的最小的元素(显然我们不能显式地维护所有 $endpos$ 集合)。
 
 为了维护 $firstpos$ 这些位置,我们将原函数扩展为 `sa_extend()` 。当我们创建新状态 $cur$ 时,我们令:
 
@@ -419,13 +401,13 @@ $$
 firstpos(cur)=len(cur)-1
 $$
 
-;当我们将点 $q$ 复制到 $clone$ 时,我们令:
+;当我们将点 $q$ 复制到 $clone$ 时,我们令:
 
 $$
 firstpos(clone)=firstpos(q)
 $$
 
-(因为值的唯一其它选项 $firstpos(cur)$ 肯定太大了)。
+(因为值的唯一的其它选项 $firstpos(cur)$ 显然太大了)。
 
 那么查询的答案就是 $firstpos(t)-length(P)+1$ ,其中 $t$ 为对应字符串 $P$ 的状态。单次查询只需要 $O(length(P))$ 的时间。
 
@@ -433,7 +415,7 @@ $$
 
 > 问题同上,这一次需要查询文本串 $T$ 中模式串出现的所有位置。
 
-我们还是为文本串 $T$ 构造后缀自动机。与上一个问题相似地,我们为所有状态计算位置 $firstpos$ 。
+我们还是为文本串 $T$ 构造 SAM 。与上一个问题相似,我们为所有状态计算位置 $firstpos$ 。
 
 如果 $t$ 为对应于模式串 $T$ 的状态,显然 $firstpos(t)$ 为答案的一部分。需要查找的其它位置怎么办?我们使用了含有字符串 $P$ 的自动机,我们还需要将哪些状态纳入自动机呢?所有对应于以 $P$ 为后缀的字符串的状态。换句话说我们要找到所有可以通过后缀链接到达状态 $t$ 的状态。
 
@@ -443,9 +425,9 @@ $$
 
 我们只需要考虑两个可能有相同 $endpos$ 值的不同状态。如果一个状态是由另一个复制而来的,则这种情况会发生。然而,这并不会对复杂度分析造成影响,因为每个状态至多被复制一次。
 
-此外,如果我们不从被复制的节点输出位置,我们也可以去除重复的位置。事实上对于一个状态,如果经过被复制状态可以到达,则经过原状态也可以到达。因此,如果我们给每个状态记录标记 `is_clone` ,我们就可以简单地忽略掉被复制的状态,只输出其它所有状态的 $firstpos$ 的值。
+此外,如果我们不从被复制的节点输出位置,我们也可以去除重复的位置。事实上对于一个状态,如果经过被复制状态可以到达,则经过原状态也可以到达。因此,如果我们给每个状态记录标记 `is_cloned` ,我们就可以简单地忽略掉被复制的状态,只输出其它所有状态的 $firstpos$ 的值。
 
-以ä¸\8bæ\98¯å®\9eç\8e°ç\9a\84æ¡\86æ\9e
+以ä¸\8bæ\98¯å¤§è\87´ç\9a\84å®\9eç\8e°
 
 ```cpp
 struct state {
@@ -454,7 +436,7 @@ struct state {
   vector<int> inv_link;
 };
 
-// 在构造后缀自动机
+// 在构造 SAM 
 for (int v = 1; v < sz; v++) {
   st[st[v].link].inv_link.push_back(v);
 }
@@ -470,7 +452,7 @@ void output_all_occurrences(int v, int P_length) {
 
 > 给定一个字符串 $S$ 和一个特定的字符集,我们要找一个长度最短的没有在 $S$ 中出现过的字符串。
 
-我们在字符串 $S$ 的后缀自动机上做动态规划。
+我们在字符串 $S$ 的 SAM 上做动态规划。
 
 令 $d[v]$ 为节点 $v$ 的答案,即,我们已经处理完了子串的一部分,当前在状态 $v$ ,想找到不连续的转移需要添加的最小字符数量。计算 $d[v]$ 非常简单。如果不存在使用字符集中至少一个字符的转移,则 $d[v]=1$ 。否则添加一个字符是不够的,我们需要求出所有转移中的最小值:
 
@@ -484,18 +466,18 @@ $$
 
 > 给定两个字符串 $S$ 和 $T$ ,求出最长公共子串,公共子串定义为在 $S$ 和 $T$ 中都作为子串出现过的字符串 $X$ 。
 
-我们为字符串 $S$ 构造后缀自动机
+我们为字符串 $S$ 构造 SAM
 
-我们现在处理字符串 $T$ ,对于每一个前缀都在 $S$ 中寻找这个前缀的最长后缀。换句话说,对于每个字符串 $T$ 中的位置,我们想要找到这个位置结束的 $S$ 和 $T$ 的最长公共子串的长度。
+我们现在处理字符串 $T$ ,对于每一个前缀都在 $S$ 中寻找这个前缀的最长后缀。换句话说,对于每个字符串 $T$ 中的位置,我们想要找到这个位置结束的 $S$ 和 $T$ 的最长公共子串的长度。
 
-为了达到这一目的,我们使用两个变量, **当前状态**  $v$ 和 **当前长度**  $l$ 。这两个变量描述当前匹配的部分:它的长度和它们对应的状态。
+为了达到这一目的,我们使用两个变量, **当前状态** $v$ 和 **当前长度** $l$ 。这两个变量描述当前匹配的部分:它的长度和它们对应的状态。
 
 一开始 $v=t_0$ 且 $l=0$ ,即,匹配为空串。
 
 现在我们来描述如何添加一个字符 $T[i]$ 并为其重新计算答案:
 
 -   如果存在一个从 $v$ 到字符 $T[i]$ 的转移,我们只需要转移并让 $l$ 自增一。
--   如果不存在这样的转移,我们需要缩短当前匹配的部分,这意味着我们需要按照以下后缀链接进行转移:
+-   如果不存在这样的转移,我们需要缩短当前匹配的部分,这意味着我们需要按照后缀链接进行转移:
 
 $$
 v=link(v)
@@ -545,20 +527,20 @@ $$
 T=S_1+D_1+S_2+D_2+\cdots+S_k+D_k.
 $$
 
-然后为字符串 $T$ 构造后缀自动机
+然后为字符串 $T$ 构造 SAM 
 
-现在我们需要在自动机中找到存在于所有字符串 $S_i$ 中的一个字符串,这可以通过使用添加的特殊字符完成。注意如果 $S_j$ 包含了一个子串,则后缀自动机中存在一条从包含字符 $D_j$ 的子串而不包含以其它字符 $D_1,\,\ldots,\,D_{j-1},\,D_{j+1},\,\ldots,\,D_k$ 开始的路径。
+现在我们需要在自动机中找到存在于所有字符串 $S_i$ 中的一个字符串,这可以通过使用添加的特殊字符完成。注意如果 $S_j$ 包含了一个子串,则 SAM 中存在一条从包含字符 $D_j$ 的子串而不包含以其它字符 $D_1,\,\ldots,\,D_{j-1},\,D_{j+1},\,\ldots,\,D_k$ 开始的路径。
 
\9b æ­¤æ\88\91们é\9c\80è¦\81计ç®\97å\8f¯è¾¾æ\80§ï¼\8cå®\83å\91\8aè¯\89æ\88\91们对äº\8eè\87ªå\8a¨æ\9cºä¸­ç\9a\84æ¯\8f个ç\8a¶æ\80\81å\92\8cæ¯\8f个å­\97符 $D_i$ æ\98¯å\90¦å­\98å\9c¨è¿\99æ ·ç\9a\84ä¸\80æ\9d¡è·¯å¾\84ã\80\82è¿\99å\8f¯ä»¥å®¹æ\98\93å\9c°é\80\9aè¿\87 DFS æ\88\96 BFS ä¸\8eå\8a¨æ\80\81è§\84å\88\92计ç®\97ã\80\82å\9c¨æ­¤之后,问题的答案就是状态 $v$ 的字符串 $longest(v)$ 中存在所有特殊字符的路径。
\9b æ­¤æ\88\91们é\9c\80è¦\81计ç®\97å\8f¯è¾¾æ\80§ï¼\8cå\8d³å¯¹äº\8eè\87ªå\8a¨æ\9cºä¸­ç\9a\84æ¯\8f个ç\8a¶æ\80\81å\92\8cæ¯\8f个å­\97符 $D_i$ ï¼\8cæ\98¯å\90¦å­\98å\9c¨è¿\99æ ·ç\9a\84ä¸\80æ\9d¡è·¯å¾\84ã\80\82è¿\99å\8f¯ä»¥å®¹æ\98\93å\9c°é\80\9aè¿\87 DFS æ\88\96 BFS å\8f\8aå\8a¨æ\80\81è§\84å\88\92计ç®\97ã\80\82之后,问题的答案就是状态 $v$ 的字符串 $longest(v)$ 中存在所有特殊字符的路径。
 
 ## 例题
 
--   SPOJ #7258 SUBLEX
+-   [SPOJ - SUBLEX](https://www.spoj.com/problems/SUBLEX/)
 -   [HihoCoder #1441 : 后缀自动机一·基本概念](http://hihocoder.com/problemset/problem/1441)
 
 ## 相关资料
 
-我们先给出与后缀自动机有关的最初的一些文献:
+我们先给出与 SAM 有关的最初的一些文献:
 
 -   A. Blumer, J. Blumer, A. Ehrenfeucht, D. Haussler, R. McConnell. **Linear 
       Size Finite Automata for the Set of All Subwords of a Word. An Outline of 
@@ -569,7 +551,7 @@ $$
 -   Maxime Crochemore. **Transducers and Repetitions** [1986]
 -   A. Nerode. **Linear automaton transformations** [1958]
 
-另外,在更新的一些资源里,在很多关于字符串算法的书中,都能找到这个主题:
+另外,在更新的一些资源以及很多关于字符串算法的书中,都能找到这个主题:
 
 -   Maxime Crochemore, Rytter Wowjcieh. **Jewels of Stringology** [2002]
 -   Bill Smyth. **Computing Patterns in Strings** [2003]