2026.2.2 SAM 的构建
首先感谢 Exber。
定义
SAM 是一个 DAG,其满足从起点到任意一个终止节点,路径上的边组成的字符串都是原字符串的一个后缀。所以只需要从起点出发到某一个状态,就可以匹配原字符串的任意一个子串。
endpos
定义初始节点为 \(t_0\)。
定义 \(\mathrm{endpos}(s)\) 表示字符串 \(s\) 在原串中所有出现的末尾位置构成的集合。
SAM 上的每一个点都代表了一种 endpos 的等价类,其中 \(x\) 是 SAM 上的点。\(f(x)\) 为点 \(x\) 代表的 endpos 集合。
定义 \(E(x)\) 表示 \(\{t|f(x)=\mathrm{endpos}(t)\}\)。
定义 \(\mathrm{longest}(x)\) 表示点 \(x\) 代表的等价类中最长的串。也就是 \(\max\{|s|,s\in E(x)\}\)。额外定义 \(\mathrm{len}(x)=|\mathrm{longest}(x)|\)。
parent
定义 \(\mathrm{parent}(x)\) 为字符串 \(\mathrm{longest}(x)\) 的一个最长的后缀,满足其所属的等价类不是 \(x\),那么这个等价类就是 \(\mathrm{parent}(x)\)。这个指针构成了一棵树。
对 parent 的解释:类似失配,随着当前 \(\mathrm{longest}(x)\) 的左字符不断被删去,endpos 集合可能会扩大,导致等价类出现第一次“改变”。而后面的改变在第一次改变后,所以跳 \(x\) 到 \(t_0\) 的树链会枚举到所有的“改变”,也相当于枚举到了所有的后缀。
线性构造(一)
考虑当前全串 \(s\) 所在的等价类 \(nw\)。代表的 endpos 为 \(\{|s|\}\)。
现在转移 \(s+c\to s'\)。其中 \(c\) 是新加入的字符。
那么一定会出现一个新等价类 \(cur\),代表的 endpos 为 \(\{|s|+1\}\)。
首先 \(nw\) 有一条连向 \(cur\),边权为 \(c\) 的边。接下来我们尝试去考虑所有 \(|s|\in f(y)\) 的点 \(y\),也就是 endpos 集合包含了串尾的点。这样的点 \(y\) 可以通过跳 \(nw\) 的 parent 指针找到。
- 若 \(y\) 没有边权为 \(c\) 的边,则可以连一条 \(c\) 的边给 \(cur\)。这意味着这些 endpos 包含了串尾的点可以转移到新点。
- 那如果 \(y\) 有边权为 \(c\) 的边呢?先处理一下这个点吧。
线性构造(二)
现在,全串有一个后缀 \(s_1\),其在一个等价类 \(p\) 中是最长串,且其有边权为 \(c\) 转移的边。
这意味着这个后缀在原串出现过多次,并且有一次出现的下一个字符恰好就是 \(c\)。记这个等价类对应的点为 \(p\),有边 \(p\to q\),且边权为 \(c\)。
如果 \(\mathrm{len}(q)=\mathrm{len}(p)+1\),这意味着 \(\mathrm{longest}(q)=s_1+c\)。此时 \(q\) 加入了 \(c\) 的影响后还是一个等价类(只不过增加了新的位置 \(|s|+1\)),只需要令 \(\mathrm{parent}(cur)=q\)。
否则呢?
此时,我们的 \(q\) 对应的串会显著的分成两类:
- \(s_1'+c\) 一类,其中 \(s'_1+c\) 是 \(s_1+c\) 同等价类的某些后缀。其出现的位置集合 endpos 可以包含新的位置 \(|s|+1\)。
- \(s_2+s_1+c\) 一类,其中由于 \(s_2\) 无法在全串的后缀继续匹配(可以用反证法证明),所以无法再包含新的位置 \(|s|+1\)。
此时我们需要分裂 \(q\) 变为 \(q\) 和 \(q_1\)。\(q_1\) 是接纳了 \(|s|+1\) 的新状态。
\(q_1\) 的连边与 \(\mathrm{parent}\) 都不变。\(\mathrm{len}(q_1)=\mathrm{len}(p)+1\),且 \(\mathrm{parent}(cur)=q_1\)。
接下来处理祖先。对于 \(p\) 和 \(p\) 的祖先 \(u\),暴力跳找到所有 \(u\) 有 \(c\) 的转移到了 \(q\),现在 \(q\) 太长了无法接纳新的 \(|s|+1\),所以这个转移其实属于 \(q_1\),如果发现这个条件不满足则终止跳跃就好。显然更上面的祖先对构建已经没有任何影响。
于是我们完成了 SAM 的构建。另外,如果找到 \(t_0\) 都没有边权为 \(c\) 的边,则直接令 \(\mathrm{parent}(cur)=0\) 就好了。

浙公网安备 33010602011771号