P4022 [CTSC2012] 熟悉的文章

蒟蒻·廖子阳發表於2024-03-09

我永遠喜歡資料結構。

不會 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\) 表示這一段是開頭),有:

\[f_j=\max\left\{\max\limits_{k\in[0,j-\text{mx}_j)\cup(j-L_i,j)\cup\mathbb{Z}}f_k,\max\limits_{k\in[j-\text{mx}_j,j-L_i]\cup\mathbb{Z}}f_k+j-k\right\} \]

就是去考慮這一段能否成為“\(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 艹過去了。

AC Link

AC Code

相關文章