Description
给定
n
个字符串,询问每个字符串
n,∑Si≤105
Analysis(SAM)
首先所有串之间连个符号,一起建个SAM。
然后枚举每一个串,把它所有的子串所在的状态的出现次数+1。
就是
parent
树上
root
到它所有状态+1,但是这样在一个串中会重复+1,所以加到一个当前串中加过的状态就不加了。
这个如果是一维数据结构就离线排序,在树上就按 dfn 序排序。
然后有个小坑,我们想要的一个状态代表的字符串的数目, 不再是 lenp−lenfap ,因为字符串有可能包含那个符号,所以要重新遍历一次计算一个状态代表的数目,这个就是 cntv=∑cntu(gou,c=v) 。
现在计算答案,由串来走,走到一个状态,答案的贡献应该是 root 到它的所有代表的数目(若出现次数大于 k )。
然而看到某人的博客,不用连起来建SAM,只需建完一个字符串把
有一个小问题:
走到一个结点,当前走过的串是否是该结点的最长子串呢?
答案是肯定的,若不是该结点的最长子串,
但该结点的
Right
集合包含当前的结尾,所以矛盾。
时间复杂度: O(N log N) 。
Analysis(SA)
首先,一样地,所以的串连起来,中间用一个没出现过的字符,构建SA。预处理出 stepi ,表示 SAi 最少往上多少,使得出现了 k 个字符串。
枚举某个字符串,计算其
时间时间复杂度: O(N log N) 。
Summary
虽然SAM做法说起来有点麻烦,但我觉得这种处理方式还是很机智的,有很多值得一提的地方。
- 按 dfn 序来操作
- 重新计算每个状态代表的字符串
- 沿 parent 树更新的思想
- 连起来一起建SAM时,走到一个状态,当前一定走出其代表的最长子串
而SA做法因有把所有后缀排序的先天优势,所以思路看起来比较简单,但也有值得一提的地方。
- 在SA上二分合法位置