用SAM维护多个串的根号特技
转载信息
抄自这里。
其实是懒得自己写
基本介绍
在多个串的字符串题中,往往会出现一类题需要用到某个子串是否在一些母串中出现。此时对于parent
树的right
集合而言,问题并不关心某个具体位置而只关心是否有某个endpos
在指定母串中。
那么对于parent
树上的来自同一个母串的节点而言,其对祖先的贡献都是可以替代的,并不需要重复标记其某个祖先right
集合中是否存在一个endpos
来自这个母串。
于是我们维护来自这个母串的所有节点对parent
树的贡献复杂度等价于所有来自这个母串的叶子节点在parent
树上的并的大小。(此处叶子指的是来自这个母串且其子树内不存在来自这个母串节点的节点)
分析这些叶子在树上的并的大小的具体规模,有一个显然的上界就是 \(\sum_{i=1}^n{2len_i-1}\) ,其中 \(len_i\) 是第 \(i\) 个串的长度,对其建后缀自动机的节点数上界为 \(2len_i-1\) ,对 \(n\) 个串一起建可以得到这个显然的上界。
观察这棵并起来的树的性质可以发现,这棵树上的每一个节点的right
集合都包含来自这个母串的endpos
,而每个节点的父节点都是该节点所代表串的一个后缀且至少长度减少1
。那么考虑这个串每一个前缀的最大贡献就是这个前缀的长度,所以可以得到另外一个上界 \(len_i^2\) 。
假设这 \(n\) 个串的总长为 \(S\) ,那么 \(2S=\sum_{i=1}^n{2len_i-1}\) ,所以对于每一个母串都维护贡献的复杂度是 \(\sum_{i=1}^n{\min(2S,len_i^2)}\) ,且 \(\sum_{i=1}^n len_i=S\) 。根据均值不等式可以得到总复杂度上界为 \(S\sqrt{2S}\) 。
然而事实上后面那个下界不容易卡满,因为你需要构造一个字符串其每一个前缀都能在parent
树上分支出最长的一条链,所以这个根号实际上跑起来比某些大常数的一个log
做法还快。
例题
BZOJ3277 字符串
题意:给出 \(n\) 个字符串,对于每一个字符串求出其有多少个子串在至少 \(k\) 个字符串中出现过。
可以说是这个特技的模板题了,不过线段树合并可以做 \(O(nlogn)\) ,这里就只说根号做法吧。建出后缀自动机后枚举每一个串的叶子节点暴力往上跳给祖先节点出现在不同的母串的次数 \(+1\) 即可,已经被别的该串节点遍历过就跳出,复杂度就是上述的 \(O(S\sqrt{2S})\) 。
NOI2018 你的名字
题意:对于每一个询问串,求出其有多少个不同子串在母串的 \([L,R]\) 之间出现过。
将问题转化为求有多少个子串在询问串和(母串的 \([L,R]\) 之间)共同出现即可,暴力枚举的同时额外加一个线段树合并判是否在 \([L,R]\) 即可,复杂度 \(S\sqrt{2S}\log{S}\) 。不过一百分因为有log
可以卡,但是68
分还是稳的。据说原作者在3s内跑过了所有测试点但是被UOJ的hack叉掉了。
Sum of Squares of the Occurrence Counts 加强版
没加强之前这个特技不能做
题意:给出 \(n\) 个串,对于每一个串 \(i\) 求出其所有子串在串 \([1,i]\) 之间的出现次数的平方和。
对于所有串建后缀自动机,用线段树合并维护出parent
树上每一个节点拥有来自哪几种母串的endpos
,以及每一种母串对应的endpos
数量。考虑每一个母串对所有线段树大小之和的贡献就是叶子的并,于是暴力遍历合并后的每一棵线段树的总复杂度就是 \(O(S\sqrt{2S})\) 。直接在线段树上暴力统计每一个parent
树上节点对每一个串的答案的贡献即可,总复杂度 \(O(S\sqrt{2S}+S\log{S})\) 。