后缀树, 后缀自动机

由于作者太菜了, 还不会任何关于后缀自动机的应用, 只会构建。

对于一个字串 \(s\), 它的每个本质不同字串, 我们定义其的 \(endpos\) 集合表示他每一个出现的地方的最后一个位置的集合。

我们又一个性质, 对于任意两个 \(s\) 的字串 \(t_1, t_2\), 他们的 \(endpos\) 要么包含, 要么不交 (即 \(endpos_A \supseteq endpos_B\ or\ endpos_B \supseteq endpos_A \ or A \cup B = \varnothing\)) 且若 \(endpos_A \supseteq endpos_B\), 则 \(B\)\(A\) 的一个后缀。

证明 1 如果两个串一个是另一个的后缀, 则他们的 $endpos$ 值是包含关系 我们假设 $A$ 是 $B$ 的字串。 则对于 $B$ 每次出现的位置, 可以把前面的字符丢掉, 这样就会出现一个字串 $A$, 所对应的$endpos$位置和 $B$ 一样。
证明 2 $endpos$ 要么包含, 要么不交 若存在两个不同的字串 $A, B$, 他们中都存在一个位置 $u$, 则一定一个串是另一个的字串, 然后就可以通过上述证明 1 证明了。
证明 3 若 $endpos_A \supseteq endpos_B$, 则 $B$ 为 $A$ 的一个后缀 一定存在一个位置 $x$, 使得 $x \in endpos_A, x \in endpos_B$, 我们就可以发现 $A$ 是 $B$ 的后缀了。

我们定义一颗叫做 \(endpos\) 树的树, 对于树上每个结点 \(u\), 存的是一种 \(endpos\), 每个结点的父亲是一个最小的集合, 使得 \(u \in endpos_{fa_u}\)

为什么这是一颗树, 对于这个点 \(u\), 如果存在两个数 \(x, y\), 使得 \(endpos_u \in endpos_x\)\(endpos_u \in endpos_y\)\(endpos_{x} \cup endpos_y \neq endpos_x \and endpos_{x} \cup endpos_y \neq endpos_y\), 由于前面两个条件, 所以 \(endpos_x \cup endpos_y \neq \varnothing\), 所以 \(endpos_x\)\(endpos_y\) 必然是包含关系。所以这是一颗树的结构。

对于树上的节点, 因为他们的 \(endpos\) 集合相等, 所以一定是一些连续的后缀。可以通过反证证明。

对于每个节点上的最小长度的字符串,把它的开头一个元素删去后的字符串一定是他父亲中, 也可以通过反证证明。

所以树上的节点数量为 \(O(n)\) 级别(每个点至少对应一个后缀)。

所以对于树上的每一个节点, 我们只需要记录这些区间的最长长度和他的父亲就可以

对于一个节点上的所以字串, 如果在字串后加入一个字符后一定到达同一个节点。

证明 如果这个节点 $u$ 加入一个字符 $c$ 到达的字串为 $x, y$ 两个不同的节点我们假设这两个节点直接的位置关系为 $endpos x \in endpos_y$ 如果存在一个位置 $z$, 使得 $z \in endpos_y$, $z \not \in endpos_x$, 则对与 $z - 1$ 位置, $endpos_y$ 的字串删掉最后一个有出现, 而 $endpos_x$ 得没有, 所以他们删掉最后一个不属于同一个集合。

我们定义转移边 \(ch_{u, c}\) 表示如果当前在节点 \(u\), 在所以当前节点位置上的字串后加一个 \(c\) 字符能达到的位置是哪。

我们已经知道了后缀自动机的基本结构, 现在我们考虑在一个字符后加入一个字符 \(c\) 时整个后缀自动机将会变成什么样。

我们考虑一个实现, 我们先新建一个节点 \(v\), 表示 \(s + c\) 所在的字符串在树上的对应的顶点找到之前的字符串 \(s\) 所在的位置, 然后不断得往上跳, 直到这个点出现一条转移边 \(ch_{u, c}\), 如果你发现这个节点 \(u\)\(ch_{u, c}\) 大小相等, 即你不需要进行额外处理。你就让 \(ch_{u, c}\) 成为 \(v\) 的父亲。

然而如果 \(u\)\(ch_{u, c}\) 大小不相等, 由于你需要维持每个节点指向的节点相同, 所以你需要把这个点拆成两个, 把原来连向这个点的边全部连向最长长度较小的那个节点。

怎么进行最后所说的把原来连向 \(u\) 的变成连向 \(new\) (假设 \(new\) 是新的点中的最多长度较小的一个)。

注意到, 能连接的是树上连续一段, 我们直接暴力修改。对于那些没有 \(ch_{u, c}\)\(u\), 让他们等于 \(v\)

你现在已经会建自动机了, 现在考虑时间复杂度。

我们考虑每一次跳一次往上的边, 你所在的节点的 \(dep_v\) 都会 \(-1\), 找到之后最多深度 \(+ 1\), 所以这个之后进行 \(O(n)\) 次。

对于修改的, 我们也考虑在上面放一个点, 由于之前每个点加 \(c\) 之后一定会对应一个点, 所在找到的那个 \(u\) 往上跳之后再经过这条边深度最多加 \(2\), 所以时间复杂度也是 \(O(n)\)

posted @ 2025-07-21 21:09  liuyichen  阅读(8)  评论(0)    收藏  举报