SAM
经过小玉米教导良久后还是不懂,遂来乱写……(这篇写的依托,建议别看,我写来自己看的)
我自己理解的定义
后缀自动机从1点出发走到任意点,路径组成的字符串是原串的子串,且原串的所有子串都可以通过这种方式得到
说白了就是储存一个串所有子串信息的一个自动ji。
出色的地方?!!
SAM的点数最多为 \(2n-1\),转移数最多为 \(3n-4\)。
你要说如何做到的?因为他把原串中出现位置相同的子串给浓缩了起来
基础
对于一个字符串 \(S\) 来说,\(\text{endpos}(T)\) 表示为 \(T\) 在 \(S\) 中出现的结尾位置集合而 SAM 要求所有 \(\text{endpos}\) 相同的子串在一个节点上,\(\text{endpos}(x)\) 表示在 SAM 上 \(x\) 点代表的子串的 \(\text{endpos}\)。
性质(写的太烂,建议不看):
-
\(\text{endpos}\) 相同的所有子串的长度集合构成一个区间,且较短串是较长串的后缀。(显然)
-
对于 SAM 上两个不同的节点,要么 \(\text{endpos}(u) \subset \text{endpos}(v)\) 或者反过来,要么 \(\text{endpos}(u)\cap\text{endpos}(v)=\varnothing\)
证明:对于两个子串来说,如果他们的某个相同长度的后缀不同,则 \(\text{endpos}\) 必定不同,否则一个串为另外一个的后缀,另外一个串出现这个串肯定会出现。
正片(也是依托)
定义 SAM 上的点 \(lst\) 代表当前处理到的后缀中最小 \(\text{endpos}\) 的串
定义一个 SAM 上的点 \(x\) 的 parent 边连向 \(y\) 使得 \(\text{endpos}(x) \subset \text{endpos}(y)\),\(|\text{endpos}(y)|\) 最小
定义 \(len_x\) 表示 SAM 上 x 点代表的最长串长度,\(minlen_x\) 是的最短串,显然 \(minlen_x=len_{parent_x}+1\)
定义 \(f_{x,c}\) 表示 SAM 上 x 点走 c 转移边到的点。
我们要构造出满足上面的并且一个点代表的串走转移边后的串集属于转移到的点的串集、一个子串不会被2个点同时表示。
用增量法构造
显然,\(lst\) 的 \(parent\) 祖先都代表当前考虑到的串的后缀,新加一个字符只会影响 SAM 上的这些点。设新加字符为 c。
新建一个点代表 endpos=i(把ci加入SAM)的,当一个点没有 c 转移边,那这个点内 所有串+c 都是第一次出现,连新建的点
当一个点有 c 转移边时,有2种情况:
第一种情况:\(len_{f_{x,c}}=len_x+1\) 不管他
否则说明 x 的 parent 子树中有点已经有 c 转移边导致 x 也有 c 转移边,设 \(o=f_{x,c}\)。
此时 o 点肯定不合规,因为 x 里某个串+c 的 endpos 增加了,他 parent 子树内的串+c 的 endpos 不变,并且都连向 o。
所以我们把 o 分裂,新建点 p 复制 o 的信息,首先可以发现转移到同一个点的SAM上点,在parent树上一定是条链,在这条链中有一段是 x 和 x 的祖宗,都连上 p,并把 o 的 parent 边连上p(o是p的后缀),\(len_x+1\to len_p\)。
SAM中点最多为2n-1个,一开始1个点,新建前两个字符最多+1节点,\(2n+1-2=2n-1\),边数最多 3n-4,时间复杂度\(O(n\sum)\)(不会证)
应用
- 不同子串个数/长度总和
根据定义求出 \(\sum len_x-len_{parent_x}\)
长度总和差不多,等差数列求和
- 每个子串出现次数/所有出现位置和相应信息
相当于是对于每一个 SAM 的点中,求 \(|\text{endpos}|\)
在parent树中,子树的endpos属于我的endpos,只需要在新增字符的时候把 endpos={i} 的 cnt++,每个点的parent对自己连边,那么像遍历树一样,\(cnt_x+=cnt_{son_x}\)
如果要求所有出现位置和相应信息的话要用可持久化权值线段树合并,cnt++改成往线段树里面的包含i点的点++,儿子的线段树合并起来就是父亲的了,只需要合并的时候,两颗线段树都有的点额外新建点记录,总时间复杂度就是 \(O(n\log n)\)(因为操作跟原本线段树合并无异,线段树没变化,合并时该遍历多少点就遍历多少,只不过遍历的时候额外新增点罢了)
说起这道题也太恐怖了,调了114514h,但lsy思路清晰把我这个fw折服了
讲一个可能只有我才会搞错的点:当一个sam上的点他的真实len<minlen_{这个点}的时候,这个点就是废的空点。只要把这些点删了,那就是还要缩点和删点的SAM
这里面每个点的真实len相当于min(区间最大pos-l+1,len)
- 第k大子串
每个节点代表串不重,只要走的路径不一样,对应的子串就不一样。所以在一个点时从a到z判断可不可以走(k<=sz_to)就可以了。
- 多串最长公共子串
选除了最短以外的串建 SAM,最短串在其他串的SAM上面跑,每次增加字符判断后缀和跑的这个串子串的最长公共串。
比如abcc和cccc的(上述内容)就是cc
比如我求出abc的(上述内容)长度为 s,在sam上的点为 \(x\),那我新加一个c,看 x 有没有 c 转移边,有就转移,s=min(s+1,真实len),否则跳parent直到有转移边,s=真实len(真实len指的是转移到的点的真实len)。
广义SAM
看不懂

浙公网安备 33010602011771号