考虑 SA,把串们用互不相同的分隔符连起来然后求。
很自然的想到,枚举每个串的每个位置(按照 SA 的顺序)作为左端点贡献。那么答案显然是所有串与它的最大后缀 lcp 中第 (k) 大的。而某个串与该后缀的最大后缀 lcp 怎么求呢,显然只可能是左右两边第一个属于该串的后缀到该位置的 (hi) 的 (min),因为再往两端扩展是单调的,这是 SA 的一个常用的性质。
如果该位置等于 (1) 的话,那就不可能往左边,那就很好办了,就找右边第一个包含 (k) 个不同串的位置然后贡献就可以了,决策是唯一的。但是普遍情况下,每个串是有两个位置可以选的。对于每个位置都对每个串算出贡献然后排序吗?这就不会维护了。
我自己 yy 出来了一个线根对的方法。考虑根号分治,小串预处理,大串实时枚举。预处理的话,就在 SA 中的每两个相邻位置之间的变化量是和该串长度成线性的,所以线根;实时枚举就比较简单了,也是线根;还要用一个 set
维护 (k) 大值。非常难写,常数也很大,没写了。
事实上遇到这种难维护的东西,可以换一个角度思考。我们不看单串们这种琐碎的东西了,我们考虑往两端扩展到哪里。那么随便推一推就会发现,对于每种往两端扩展的方案,贡献是两端之间的 (hi) 的 RMinQ。然后就把所有方案给 (max) 起来即可。
不难发现,最优的方案一定恰好包含 (k) 个不同的串,容易反证。我们考虑预处理出来这些区间们,然后很简单的差分一下 multiset
扫一遍。但是又发现,这些区间的个数很容易被卡到平方,例如 (1 o n,1 o n,1 o n)。我们考虑对这种情况该如何解决。
- 该位置在中间的话,那显然左右两段各伸出去 (0) 是最好的;
- 在两边两段的话,那显然是另一段伸出去 (0) 最好。
综上可以得出结论,我们只需要预处理那些,往左极小或往右极小的区间。这显然是线性的,正反两遍 two-pointers 即可。
upd. 2021.1.4:
被毒瘤号某在模拟赛里加强到 (1.5 imes10^6) 了……原来的代码里面用了三轮 multiset
,必须都去掉。前面两个可以用 bool
数组代替,我当时脑抽了;最后一个可以线段树代替差分,或者前后两遍各跑一次单调队列(单调性显然满足)每个位置取 min
。