1003-F. Abbreviation
出處:https://codeforces.com/contest/1003/problem/F
提交:https://codeforces.com/contest/1003/submission/110702340
問題:給定 $n\in[1,300]$ 個用單空白分隔的單字 $w[i]$,保證文本全長 (含空白) 不超過 $10^5$,請問該文本經過「至多一次縮寫」之後的最短長度為何?所謂「縮寫」的意思是,挑出至少兩個以上匹配、不重疊的連續單字區間,再讓每個區間都縮減成一個「只取各個單字的第一個字母,並且刪減掉中間所有空格的新單字」。所謂兩段連續單字區間匹配的意思是,兩區間彼此之間對應位置的單字皆相同,例如文本 “a ab a a b ab a a b c” 裡面第 2-5 區間的 “ab a a b” 和第 6-9 區間的 “ab a a b” 就是一組匹配的區間。
解法:第一時間想得到的解法,如果先不考慮 DP 怎麼設計遞迴式的話,應該是先決定一個基底區間 seg,之後往後查找最多有幾個彼此互不重疊而且與 seg 匹配的區間 [1],計算暫時解,接著再嘗試下一個基底區間,一樣會得到另一個暫時解,重複這個流程,那麼最後得到的最小暫時解就是我們要的。那麼現在最重要的問題就是,給定一個 seg,如何快速地在往後的序列中找到最多與 seg 匹配的區間?可能會有大大想使用 KMP 技巧,這邊應該也是可行,只可惜這個技巧我不甚熟悉,而且在競程的時間壓力下也應該選擇相對好理解/實作的方式,於是這邊採用一般的 DP 技巧。參考解答的作法還蠻聰明的,我可能沒辦法自己想到,它定義 DP[i][j] 是為區間 $w[i:n]$ 與 $w[j:n]$ 以「$w[i]=w[j]$」為首的最長匹配子區間長度,如果 $w[i]\ne w[j]$ 則長度為 0,填表順序可以每次固定 i 和 j 的差距再由後往前 bottom up 得到,在此便不贅述。有了這個表之後,我們就能快速地得到想要的答案,舉例來說,我希望在 $w[i:n]$ 尋找以 $w[i:i+(s-1)]$ 為基底區間的答案,那麼我其實可以每次觀察 DP[i][j] 的值,j 從 i+s 起跳,如果它不小於 s,代表我可以立刻再取出一段長度 s 的匹配區間並把 j 更新為 j+s;如果它小於 s,代表我無法取出以 $w[j]$ 為首的匹配區間,那麼 j 就更新為 j+1,並繼續搜尋,過程結束後所有蒐集到的區間就組成最多匹配區間,此時就可以算暫時最佳解。而所有暫時最佳解裡面最小的那個就是我們要的答案!
附註:
[1] 要從一個大區間內找到最多的指定子區間,一般大家認為 greedy 的方式就是從左往右一路掃過去,一遇到符合的子區間就納入答案中,接著再跳往該子區間末尾的下一個字串繼續搜尋,而這個方法其實也是正確的。但證明魔人可能很好奇,如果先暫時略過一個符合的子區間,繼續往下查找,有沒有可能找到更多呢?答案是不會的。簡單證明如下:如果我不放過任何一個機會,假設這樣子是從 $w[i_1:j]$ 的區間找答案;如果我稍稍放過一些機會,假設這樣子是從 $w[i_2:j]$ 的區間找答案,那麼我們便可以說 $i_1\ge i_2$,此時因為我至少可以把 $w[i_2:j]$ 的最優選擇直接平移到 $w[i_1:j]$ 裡面,或者換句話說,$w[i_1:j]$ 的這個區間一定包含 $w[i_2:j]$ 的最優組合,所以吾人可以推得 $w[i_1:j]$ 的最優解數量勢必不小於 $w[i_2:j]$ 的最優解數量的結論。
實作:
|
|
結果: