P4022 [CTSC2012] 熟悉的文章
不会 SAM,来一个大常垃圾 SA 做法。
给出 \(n\) 个文本串 \(s_1\sim s_n\) 和 \(m\) 个询问串 \(t_1\sim t_m\)。
称一个字符串 \(\text{str}\) 是“\(L\) 熟悉的”,当且仅当 \(|\text{str}|\ge L\),且 \(\text{str}\) 是文本串的子串,此时记 \(P(\text{str},L)=1\)。否则 \(P(\text{str},L)=0\)。
对于每个询问串 \(t_i\),求出最大的整数 \(L_i\),使得将其划分为若干个子串后,所有“\(L_i\) 熟悉的”子串长度之和不小于 \(\dfrac{9|t_i|}{10}\)。
形式化地,记一种划分 \(t_i\) 的方式为 \(S=\{[l_1,r_1],\dots,[l_{|S|},r_{|S|}]\}\),满足 \(\forall \,j\in[1,|S|)\cup \mathbb{Z},r_j+1=l_{j+1}-1\),且 \(l_1=1,r_{|S|}=|t_i|\)。记所有划分方案构成的集合为 \(U\)。你要找到最大的整数 \(L_i\),满足 \(\exists\,T\in U,\sum\limits_{j=1}^{|T|}[P(t_i[l_j,r_j],L_i)\cdot (r_j-l_j+1)]\ge \dfrac{9|t_i|}{10}\)。
记 \(N=\sum\limits_{i=1}^n|s_i|,M=\sum\limits_{i=1}^m|t_i|\),满足 \(N,M\le 1.1\times 10^6\)。
\(\text{1 s / 250 MB}\)。
默认 \(\mathcal{O}(n)=\mathcal{O}(m)=\mathcal{O}(N)=\mathcal{O}(M)\)。字符集大小 \(\mathcal{O}(|\Sigma|)=\mathcal{O}(1)\)。
对于每一个询问,容易发现答案有单调性,因为若 \(x\) 是合法的,则在 \(x-1\) 时仍然按照这种方式划分,式子的值是不减的。所以二分答案。
对于一个已知的 \(L_i\),我们可以 dp 求出上面式子的最大值然后判断是否合法。
设 \(f_j\) 表示将 \(t_i[1,j]\) 分成若干段,上面式子的最大值。记 \(\text{mx}_j\) 表示以 \(t_i[1,j]\) 为前缀的最长后缀 \(\text{suf}\) 的长度,使得 \(\text{suf}\) 在文本串中出现过。那么考虑枚举上一段的末尾(为 \(0\) 表示这一段是开头),有:
就是去考虑这一段能否成为“\(L_i\) 熟悉的”。容易发现 \(f_j\ge f_{j-1}\),因为我将 \(j\) 这个位置单独分一段,答案是不减的。所以前一部分的转移可以用 \(f_{j-1}\) 代替。
至于后面那部分,先将 \(\text{mx}_j\) 求出来。
将所有串用分隔符拼在一起形成大串 \(A\) 进行后缀排序。我们记 \(\text{MX}_k\) 表示 \(A[1,k]\) 这个前缀最长的后缀,使得它在文本串中出现。可以发现 \(\text{mx}_j\) 就等于 \(t_{i,j}\) 在 \(A\) 中对应位置的 \(\text{MX}\) 值,因为 \(A[1,k]\) 存在。因为 \(A[1,k]\) 中存在 \(\text{mx}_j\) 长度的后缀满足条件,且不存在更长的后缀满足条件。
进一步发现,若 \(A[1,k]\) 存在长度为 \(a\) 的后缀满足条件,则长度为 \(a-1\) 的后缀也满足条件。因为后者是前者的子串。所以初步想法是二分 \(\text{MX}_k\),然后判断是否合法。
考虑如何判断一个长度 \(\text{len}\) 是否合法。若合法当且仅当 \(A\) 中存在一个来自于文本串的后缀,使得它与 \(A[k-\text{len}+1,|A|]\) 这个后缀的最长公共前缀长度不少于 \(\text{len}\)。
满足后面那个条件的后缀排名形如一段区间 \([\text{ql},\text{qr}]\),可以二分 + \(\text{height}\) 数组 rmq 得到。记 \(B_i=1/0\) 表示排名为 \(i\) 的后缀是否来自文本串。那么判断 \(B\) 数组的区间和是否为正即可,前缀和维护。
但是这样对于每个后缀做一遍时间复杂度为 \(\mathcal{O}\left(n\log^2 n\right)\),无法接受。
注意到一个关键性质,\(\text{MX}_k\ge \text{MX}_{k+1}-1\)。因为 \(A[1,k+1]\) 的那个后缀长度为 \(\text{MX}_{k+1}-1\) 的前缀是 \(A[1,k]\) 的后缀。
那么这样用个指针扫一下即可,指针最多递增 \(\mathcal{O}(n)\) 次,所以这样时间复杂度是 \(\mathcal{O}(n\log n)\)。
这样我们就求出了 \(\text{mx}_j\)。根据上面那个性质,可以发现第二类转移区间左端点 \(j-\text{mx}_j\) 是不降的。同时右端点 \(j-L\) 也是不降的。那么对于这样的区间求 \(f_k-k\) 最大值,单调队列维护即可。
时空复杂度均为 \(\mathcal{O}(n\log n)\)。可以把二分 + ST 表换成线段树上二分做到线性空间,但是常数太大无法接受。一开始一直在为空间纠结,但事实上并不卡空间。
各种卡常、指令集配合 C++98
艹过去了。