從 vim 一題看線頭 DP 的擴展應用


線頭 DP

我們知道,線頭 DP 是我們搞定一類只和兩側元素有關的排列問題的利器。

我們在數軸上,將排列中相鄰的兩個數連線,這樣得到一條折線。以邊為單位,考察穿過這條邊的折線段的影響,進行動態規划。

線頭 DP 在更廣泛的折線路徑類問題上的應用

我們以「BalticOI 2013」Vim 為例分析線頭 DP 的擴展應用。

既然我們知道,線頭 DP 先將排列轉為了折線,我們就可以拿它來解決更廣泛的折線路徑類問題。

在本題中,我們可以將光標的移動視為折線。注意到我們刪除一個 $e$,一定是在該 $e$ 之后的那個位置進行操作 hx。因此所有的 $e$ 都需要兩步,這樣我們刪去那些 $e$,問題轉化為經過一些關鍵點(原左邊為 $e$ 的位置)的最小步數了。

我們將 f 操作引發的折線叫作飛線,h 操作引發的折線叫作跳線。我們知道,折線路徑一定是這樣的:每次先通過若干次飛線連成的飛鏈降落在某點,然后往回跳到出發點右側第一個關鍵點。

為了計算答案的清晰性,我們認為每次操作的步數都在起點計算。

從兩個點之間的間隙將折線剪開,至多會剪斷兩條飛線和一條跳線。如果沒有剪到飛線,那么說明整個折線已經結束了。我們知道飛線的降落地點只和其字符有關。

於是我們設 $f(i, j, k)$ 表示在第 $i$ 個字符右側剪開折線,剪到的兩條飛線中所屬飛鏈起點較左的那條的字符為 $j$,所屬飛鏈起點較右的那條的字符為 $k$ 時,在前 $i$ 個字符內計算的步數最小值。特別地,$k=\mbox{nil}$ 表示它只穿過了一條飛線。

根據這個我們進行分類討論,設計動態規划。

由於最后一段飛鏈回跳后可以不必再引出飛線,所以要特殊考慮。我們在倒數第二段飛鏈結束的時候來計算答案。

我們利用序列自動機,求出第 $i$ 個點右邊第一個字符 $j$ 的位置 $\mbox{next}_{ij}$ 和第 $i$ 個點右邊第一個關鍵點 $\mbox{nextkey}_i$。那么,如果最后一條飛鏈在第 $i$ 個點降落,且最后一條飛鏈跳到了 $j$,設最后一個關鍵點位置為 $\mbox{lastkey}$,那么就要滿足 $j\ge \mbox{lastkey}$,回跳的步數為 $j-\mbox{nextkey}_i$。

因此我們令 $d(i)$ 表示從第 $i$ 個點經過 $s$ 條飛線降落在 $t$,使 $t \ge \mbox{lastkey}$ 時最小的 $2s+t$,進行動態規划。

於是答案就是 $\min\big\{f(i, j, \mbox{nil})+d(\mbox{next}_{ij})-\mbox{nextkey}_i \big| 1 \le i \lt \mbox{lastkey}, j \in \{a, b, c, d, f, g, h, i, j\}\big\}$。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM