后缀自动机的运用

做了一些题,主要是题目的列举和简单总结,所以讲解会很简略。

第一次写这种简略的说明,完全不用考虑别人懂不懂,好舒服!

一、基础操作

这里是一些有助于理解sam基本结构的题目,所以讲解会比较简略。

我们知道sam上每一个节点表示的是一段长度连续的子串,可以通过parent树上的简单统计得到该节点的 \(|\text{edp}(x)|\),所以该节点对答案产生的贡献就是 $len\times |\text{edp}(x)| $ 。

我们知道对于一次加入的子串 \(S[1,i],S[2,i],...,s[i,i]\) ,之前没有出现的一定是 \(S[1,i]\sim S[a,i]\)

之后考虑求出这个 \(a\),经过分析sam的加入过程可以知道,其实这就是 \(tr[x].len\) 。(这也可以直接sa搞)

二、匹配所有子串

因为sam维护了所有的子串,所以我们也可以做到匹配子串的功能,而且复杂度是优秀的 \(O(|S|)\) 的。

假设只有两个串,我们考虑把 \(S_1\) 拿出来做比配串,把 \(S_2\) 建sam,对于 \(i\in[1,|S_1|]\),求出 \(a_i\) 表⽰\(S1[1,i]\)\(S_2\) 中出现过最长后缀的长度,即以 \(i\) 结尾的位置的最⻓匹配⻓度。

这样 \(\max a_i\) 就是答案。

那么有多个串,我们就选最小的串作为匹配串,然后在剩下的每一个串 \(S_i\) 中都做一遍上面的流程,并且对 \(a_i\)\(\min\) 去更新,这样最后 \(\max a_i\) 就是答案。

复杂度任然是 \(O(\sum|S_i|)\) 的。

这里是没有区间的限制的情况。

同样可以考虑直接把 \(T\)\(S\) 的sam上进行匹配,同样求得以 \(i\) 结尾的位置的最⻓匹配⻓度。

但是我们没有考虑在 \(T\) 中去重(也就是会出现同样的子串被算多次。)

参考上面的说法,会算重的也是一段后缀,而且这个是可以通过再建一个sam求出来的,所以答案就是 \(\sum i-\max(a_i,b_i)\)

循环,就是在匹配的时候往后面加字符,并且把前面的字符删掉。

后面加是好做的,但是怎么考虑删字符?

发现这就是 \(x\)\(link\) 的操作。

三、在parent树上的操作

这一部分所涉及的操作都会通过转化成parent树上的操作,所以也有一定树上操作的基础。

差分,后面加上 \(len=1\) 的情况。

对于前缀 \(i\) 和前缀 \(j\) ,会产生贡献的答案为 \(\min(\text{lca(}id_i,id_j).len,|i-j|-1)\) 。(\(id_i\) 表示 \(S[1,i]\) 在sam上的节点编号)

那么我们考虑启发式合并,每次合并过来一个前缀 \(x\) 的时候更新答案。

合并的时候可以用线段树维护区间个数、区间权值和,来统计答案。

考虑一次询问 \(l,r\) ,我们需要对 \(id_r\) 的每一个父亲 \(x\) 考虑,对答案的贡献就是 \(\max\{i|i\in edp(x)\and i\le \max(r,l+x.len-1)\}-l\) ,我们对每一个 \(x\) 统计一下取 \(\max\) 就是答案。

然后考虑优化,考虑重连剖分,这样一个询问/修改(你可以把修改理解为我一开始会给\(id_i\) 到根的每个点的集合加入 \(i\) )都对应到了 \(\log\) 个链的情况,接下来我们考虑处理链的情况。

因为 \(x.len\) 随深度减少而减少,所以一个修改如果会产生贡献那么一定会在与查询的 \(lca\) 处。

所以我们可以先从链顶从上往下扫,对每一个询问处理修改在他上面的情况,在从链顶从下往上扫,处理修改在下面的情况。

从下往上扫是好处理的,直接用set支持插入、查询前驱即可。

考虑从上往下扫的情况,这时每个修改会多一个权值 \(b_i\) 表示插入时的点的 \(len\) ,而一个修改查询的是满足 \(i\le r\and i+b_i\le l\) 的最大的 \(i\),二维数点?树套树?实际上这是可以用线段树二分解决的。

所以整个题就可以 \(O(n\log^2 n)\) 解决了。

四、用线段树合并维护endpos

标题的意思,有了这个我们就可以干很多关于具体的edp位置的事。

考虑之前我们的做法需要干什么:考虑当前在sam上的状态有没有在给定区间中出现过。

设当前匹配长度为 \(len\) ,那么就是看edp在 \([l+len-1,r]\) 中有没有元素,这是很好办的。

注意:这里发现没有不能直接跳link,因为你看这个区间的范围就知道是关于len的,所以我们要每一给 \(len\) 减一,然后继续。虽然每次只减一看起来很慢,但是复杂度还是 \(O(\sum|T|\log |S|)\) 的。

看起来很神秘,需要你好好想想循环的串的性质(或许也不叫性质)

一个循环的串 ABBAABBAABBA 可以拆成两个相同的串:

ABBAABBA
    ABBAABBA

诶,这样对于一个sam上的节点 \(i\) ,设 \(p=edp(i)\) 中相邻的举例最近的距离,那么产生的贡献就是 \(\lfloor\frac{len}{p}\rfloor+1\) 了。

\(p\) 直接用线段树维护就行了(好像也可以用启发式合并+链表)

五、广义sam

这里指用trie建出来的广义sam,这个的性质会比其他的广义sam好。

正牌广义sam建出来,我们只用维护一下sam上的节点的edp来自几个不同的串,相当于只用求子树内的颜色数是否大于2,求出来就可以直接给 \(ans_{co_i}\leftarrow ans_{co_i}+i.len-i.link.len\) 就行了。

之前做的,可能会有记忆缺失。

同样把每个sam的节点用线段树维护在哪个子串中出现了多少次(合并时用线段树合并),然后用倍增找到给定区间所在的sam节点,然后就直接询问找区间最值。

posted @ 2022-06-01 15:32  qwq_123  阅读(114)  评论(0编辑  收藏  举报