\( \newcommand{\ord}[1]{\mathcal{O}\left(#1\right)} \newcommand{\abs}[1]{\lvert #1 \rvert} \newcommand{\floor}[1]{\lfloor #1 \rfloor} \newcommand{\ceil}[1]{\lceil #1 \rceil} \newcommand{\opord}{\operatorname{\mathcal{O}}} \newcommand{\argmax}{\operatorname{arg\,max}} \newcommand{\str}[1]{\texttt{"#1"}} \)

動態規劃題解

Dynamic Programming solve

馬勒戈壁的草泥馬分割

這題是給一個數字$n$,給出他所有分割,你要求按造字典序由大到小第$m$個分割
這題因為$n \leq 300$,所以$m$是long long範圍
當然直接暴力去分割一定TLE,但是求出第$m$個分割好像有困難?
先求出總共有幾個分割吧!總共有幾個都求不出來了,還要求第$m$個?

總共有多少分割?

這裡我提供我的作法
設$dp(pa,x)$表示你有$x$個東西可以分割
但是分割出來的最元素不會超過$pa$
而數字$n$的所有分割的答案就會是$dp(n,n)$
舉例來說,$dp(5,7)=13$,這13種分法如下:
5 2
5 1 1
-------------
4 3
4 2 1
4 1 1 1
-------------
3 3 1
3 2 2
3 2 1 1
3 1 1 1 1
-------------
2 2 2 1
2 2 1 1 1
2 1 1 1 1 1
-------------
1 1 1 1 1 1 1
$dp(5,2)=2$
2
1 1
$dp(4,3)=3$
3
2 1
1 1 1
$dp(3,4)=4$
3 1
2 2
2 1 1
1 1 1 1
$dp(2,5)=3$
2 2 1
2 1 1 1
1 1 1 1 1
$dp(1,6)=1$
1 1 1 1 1 1
有發現甚麼嗎?
$dp(5,7)=dp(5,2)+dp(4,3)+dp(3,4)+dp(2,5)+dp(1,6)$

狀態轉移式

根據觀察,可以得到以下的狀態轉移式
$$dp(pa,x)=\sum_{i=0}^{min(pa,x)} dp(i,x-i)$$
別忘了邊界條件:$dp(pa,0)=1$
狀態有$\ord{n^2}$個,轉移$\ord{n}$
這樣我們就得到一個$\ord{n^3}$的DP
這題$n$最大是300,不會TLE
但是我們要找的是第$m$個分割啊,不是所有的數量

狀態回推解答

每個狀態,都是透過其他狀態轉移來的
所以可以根據你狀態的意義來回推解答

助教判斷抄襲的方法

這題考的其實是最小編輯距離
是非常經典的題目
大部分用來判斷抄襲的方式都跟這很類似
這個DP的狀態也很好設計
令$dp(x,y)$為把$A[0 \sim x]$修改成$B[0 \sim y]$的最小編輯距離
顯然$dp(|A|-1,|B|-1)$就會是答案

困難點在於狀態轉移要怎麼設計

狀態轉移

我們把插入、刪除、修改分開來討論

$A[x]==B[y]$

這個時候$dp(x,y)=dp(x-1,y-1)$應該很好理解吧

$A[x]!=B[y]$

可以知道在$A[x]!=B[y]$時,$dp(x,y)$就會是插入、刪除、修改所需的花費中最小的花費

插入

我們的目標是要把$A[0 \sim x]$以最小費用修改成$B[0 \sim y]$

插入可以想成先把$A[0 \sim x]$以最小費用修改成$B[0 \sim y-1]$
之後在尾端插入$B[y]$

因此插入的最小代價為$dp(x,y-1)+c0$

刪除

我們的目標是要把$A[0 \sim x]$以最小費用修改成$B[0 \sim y]$

刪除可以想成先把$A[0 \sim x]$刪除尾端元素成$A[0 \sim x-1]$後
在把刪除後的結果以最小費用修改成$B[0 \sim y]$

因此刪除的最小代價為$c1+dp(x-1,y)$

修改

我們的目標是要把$A[0 \sim x]$以最小費用修改成$B[0 \sim y]$

修改可以想成先把$A[0 \sim x-1]$以最小費用修改成$B[0 \sim y-1]$
再把$A[x]$修改成$B[y]$

因此修改的最小代價為$dp(x-1,y-1)+c2$

狀態轉移式

根據剛才我們的分析,可以得到以下的狀態轉移式
$$ dp(x,y)=\left\{ \begin{array}{} dp(x-1,y-1) & & {A[x]==b[y]}\\ min\left( \begin{array}{rcl} dp(x,y-1)+c0 \\ c1+dp(x-1,y) \\ dp(x-1,y-1)+c2 \end{array} \right) & & {A[x]!=b[y]} \end{array} \right. $$
實作時別忘了邊界條件啊!

最常共同子序列

最常共同子序列是經典題目
隨便上網應該都可以找的到答案
只是這裡我出的是三個字串的版本

以下簡稱最常共同子序列為LCS

定義狀態

很明顯的,這題狀態會是三維
令$dp(x,y,z)$表示$A[0 \sim x]$、$B[0 \sim y]$、$C[0 \sim z]$的LCS長度

狀態轉移

跟只有兩個字串的狀況很相近
我們可以分成兩個case討論

$A[x]==B[y]==C[z]$

很明顯這時候$dp(x,y,z)=dp(x-1,y-1,z-1)+1$

前項條件不滿足時

$A[x]$、$B[y]$、$C[z]$不會完全相同
表示把某個不一樣的元素刪掉,其LCS不變
我們來看個例子
令$ A=abcbbbbba\\ B=abbbcbbac\\ C=acbbbbbba $
很明顯的其LCS只有一個為$abbbbba$

但我們可以把$B$的最尾端元素去掉
令$B^{'}=abbbcbba$
則$A,B^{'},C$的LCS同樣是$abbbbba$

所以我們只要找到那個不一樣的元素
把它刪掉,用剩下來的東西去求LCS結果會是不變的

這個性質跟DP有甚麼關係?
我們可以找出把$A[x]$、$B[y]$、$C[z]$刪掉情況下最長的LCS
也就是$dp(x-1,y,z)$、$dp(x,y-1,z)$、$dp(x,y,z-1)$的最大值

其結果就會是$dp(x,y,z)$的值

狀態轉移式

根據剛剛的推論,我們可以得到以下的狀態轉移式
$$ dp(x,y,z)=\left\{ \begin{array}{} dp(x-1,y-1,z-1)+1 & & {A[x]==b[y]==C[z]}\\ max\left( \begin{array}{rcl} dp(x-1,y,z) \\ dp(x,y-1,z) \\ dp(x,y,z-1) \end{array} \right) & & {else} \end{array} \right. $$

金坷垃生產基地

這題是經典的旅行銷售員問題
如果聽完我講解還不懂的話可以上網查
這題我們$n$最多只有16而已
大家可能一開始會想到一個$\ord{n!}$的方法
枚舉所有的排列,找出路徑最小的

很不幸$16!=20922789888000$ 會TLE

試著用DP?

通常遇到$n$很小的題目
不是暴力硬幹,就是狀態壓縮DP

狀態設計

我們從1出發
設$dp(x,S)$表示在已經走了集合$S$中的所有元素
最後停在$x$這個生產基地的最短路徑長度
(走的路徑不會是一個環)

注意$S$是一個集合,會用到集合相關的操作
舉例來說,$dp(8,\{2,5,8,10\})$
表示我從$1$開始,以某個順序經過$2,5,10$
最後停在$8$的最短路徑長度

則$dp(1,\{1 \sim n\})$就會是最終答案

狀態轉移

為了方便起見,令$dis(u,v)$表示$u$到$v$的路徑長
找出當前狀態與其他已經算過的狀態的關係
是狀態轉移的目標

可以先舉個例子,觀察它跟其他狀態的關係

以$dp(8,\{2,5,8,10\})$為例
表示我從$1$開始,以某個順序經過$2,5,10$
最後停在$8$的最短路徑長度

在到達$8$之前最後到達的點一定會是$2,5,10$其中一個

假設在到達$8$之前最後到達的點為5
那$dp(8,\{2,5,8,10\})$其中一條可能的路徑長度為:
$dp(5,\{2,5,10\})+dis(5,8)$
以同樣的邏輯,可以發現: $$ dp(8,\{2,5,8,10\})=min\left( \begin{array}{rcl} dp(2,\{2,5,10\})+dis(2,8)\\ dp(5,\{2,5,10\})+dis(5,8)\\ dp(10,\{2,5,10\})+dis(10,8) \end{array} \right) $$
以一般化的情況來表示
$let \; S^{'}=S-\{x\}$
$dp(x,S)=min(\{dp(u,S^{'})+dis(u,x):u \in S^{'}\})$
邊界條件?

邊界條件

當$S=\{x\}$時,$dp(x,\{x\})$的值要怎麼定義呢?

這時候$dp(x,\{x\})$表示我們從1出發
經過$x$,最後停在$x$

這不就是$dis(1,x)$嗎?

邊界條件

因此我們可以設當$S=\{x\}$時,$dp(x,S)=dis(1,x)$
這樣我們就完美的解決這個問題了

迴文自動機

這題做法非常多元化
這裡介紹兩個容易想的做法

真的有迴文自動機的存在喔,只是你不會用而已

方法一 找出最長迴文子序列

找出最長迴文子序列,那剩下不是最長迴文子序列的元素
一定要增加新的元素來讓它成為回文
紅色部分是原本字串的其中一個最長迴文子序列
aacdaab

剩下的d,b,一定要在字串裡面加入新的d,b來補齊
baadcdaab

因為是最長迴文子序列,所以要加入的字元數會是最少的
結論:$原本問題的答案=字串長度-最長迴文子序列長度$
最長迴文子字串如果只要求長度的話
可以直接轉化成LCS問題

假設原本的字串叫$A$,令$B=$把$A$反轉後的字串
則$A$、$B$的LCS長度就會是最常迴文子序列長度

$A=aacdaab,B=baadcaa\\LCS(A,B)=5$

注意

雖然LCS長度就會是最常迴文子序列長度
但是從LCS的DP狀態回推最常迴文子序列可能會是錯的
因為會找出其他的LCS

kfclbckibbibjccbej
這個字串最長迴文是bcibbicb
但可能找出的LCS會是bcibbibc
找出一個最長迴文子序列是有其他方法的
因為不是這題重點那我就不講了

方法二 直接DP

通常一個字串會有$\ord{n^2}$以上時間複雜度的DP
那他的DP狀態常常會用一個區間來表示
這題就是一個很好的例子

狀態設計

設原本的字串為$S$
令$dp(l,r)$表示把$S[l \sim r]$變成迴文最少要插入多少字符
這題$dp(0,|S|-1)$就會是答案

狀態轉移

一樣分兩個case討論

$S[l]==S[r]$

顯然的,這個時候$dp(l,r)=dp(l+1,r-1)$
可以想成把$S[l+1 \sim r-1]$變成迴文後
在頭尾插入$S[l]$和$S[r]$仍然是迴文

$S[l]!=S[r]$

這時候S[l]跟S[r]其中一個必須要用插入的字符來構成迴文
若$S[l]$要用插入的字符來構成迴文的話
所需要的最少花費為$dp(l+1,r)+1$
也就是先把$S[l+1 \sim r]$變成迴文後,在$r$的位置後插入$S[l]$的字符
這時$S[l]$和剛剛插入的字符就會構成迴文

若$S[r]$要用插入的字符來構成迴文的話
所需要的最少花費為$dp(l,r-1)+1$
也就是先把$S[l \sim r-1]$變成迴文後,在$l$的位置後插入$S[r]$的字符
這時$S[r]$和剛剛插入的字符就會構成迴文

$S[l]!=S[r]$

因此這時候$dp(l,r)=min(dp(l+1,r),dp(l,r-1))+1$
最後可以得到其狀態轉移式為:
$$ dp(l,r)=\left\{ \begin{array}{} dp(l+1,r-1) & & {S[l]==S[r]}\\ min\left( \begin{array}{rcl} dp(l+1,r)\\ dp(l,r-1) \end{array} \right)+1 & & {S[l]!=S[r]} \end{array} \right. $$
顯然的,邊界條件是當$l==r$時
$dp(l,r)=0$

背包九講