一种很好写的 \(O(n\log ^2 n)\) 的做法和处理技巧,不需要会任何 border series 的知识,只需要会 SAM 和一些基础数据结构就行。
考虑 \(\text{MaxBorder}(l,r)\) 可以被写成即找到最大的 \(p \leq r - l\) 满足 \(S[l:l+p]=S[r-p+1:r]\),子串相等并不是我们喜欢见到的形式,考虑写成前缀的形式,即需要找到最大的 \(p \leq r - l\) 满足 \(\text{lcs}(l+p,r)\geq p\)。
建出 SAM 的 parent 树即前缀树,那么 \(\text{lcs}\) 可以被表示为前缀树上的 \(\text{LCA}\) 的 \(\text {len}\) 和 \(r-l+1\) 取 \(\min\) 后的值。
于是我们有了一种 SAM 的暴力做法:在前缀树上往上跳,对于所有满足 \(\text{len} \leq r - l\) 的点(这些点的 \(\text{lcs}\) 长度就是 \(\text{len}\))考虑其为 \(\text{LCA}\) 时的最大 \(p\),即查询其 \(\text{endpos}\) 集合中 \([l,l+\text{len}-1]\) 的最大的 \(\text{endpos}\),这个位置 \(p\) 代表的前缀在 \([l,p]\) 内的部分就(可能)是一个最大 \(\text{Border}\)。
那些 \(\text{len}>r-l\) 的节点作为 \(\text{LCA}\) 不应该被考虑,因为这些节点的意义是“整个串都是 \(\text{lcs}\)”,显然这个串本身不应该被考虑进自己的 \(\text{Border}\) 中。又因为前缀树上的 \(\text{len}\) 是单调的,可以直接倍增找第一个 \(\text{len} \leq r - l\) 的点。
我们现在将字符串的问题转化成了前缀树上的问题,如下:
-
前缀树上每个点可能对应着一个或没有的 \(\text{endpos}\) 位置。
-
每次给出 \((u,l)\),对于点 \(u\) 到根的链上的所有点 \(x\) 查询所有 \(\text{lca}(u,P)=x\) 的点的 \(P\) 的 \(\text{endpos}\) 在 \([l,l+\text{len}(x)-1]\) 内的最大值。
-
因为 \(\text{len}\) 的单调性,我们在高于 \(\text{LCA}\) 的地方统计到一个点不会影响答案(更短的 \(\text{lcs}\) 不会使得它更优),所以我们只需要保证最近公共祖先被考虑到即可,不用担心考虑到更高的公共祖先。
直接可持久化线段树合并维护 \(\text{endpos}\) 暴力跳 parent 树查询 \([l,l+\text{len}-1]\) 树高是 \(O(n)\),复杂度不正确。
因为是和 \(\text{LCA}\) 相关的信息,考虑树分治,分别考虑 \(u\) 作为 \(\text{LCA}\) 的轻子树和重子树时的情况。
\(u\) 作为轻子树仅有 \(O(\log n)\) 次,直接在这些位置的维护 \(\text{endpos}\) 的线段树上查询即可。
\(u\) 作为重子树:这个时候我们询问的是一条重链的前缀,这条重链上可能和它产生贡献的点只有这个前缀和这个前缀轻子树里边的点。因为轻子树的大小和是 \(O(n\log n)\) 的,对于每条重链直接遍历轻子树是可以接受的。
因为是前缀询问所以考虑把问题离线,询问挂在对应的点上,在对应的位置加入轻子树的点,考虑一个和这条重链的 \(\text{LCA}\) 在 \(x\) 处的轻子树内 \(\text{endpos}\) 位置 \(P\),在 \(\text{LCA}\) 处询问 \([l,l+\text{len}(x)-1]\) 能够包含这个点的 \(l\) 就在范围 \([P-\text{len}(x)+1,P]\) 内。
区间取 \(\max\),单点查即可。
回顾本题,我们本质上是使用了重链剖分作为平衡复杂度的方式
-
在轻子树从下到上维护,基于作为轻子树的询问考虑被询问对象。
-
在重链从上到下扫描,基于作为轻子树的被询问对象考虑向询问贡献。
因为轻子树的大小和是 \(O(n\log n)\),两侧使用的数据结构都是 \(O(\log n)\) 的,所以时间复杂度 \(O(n\log ^2 n)\)。