對動態規劃問題的一些思考
HDOJ刷了一些動態規劃(DP)問題,做些總結.
1 序列
目前看來,所有的動態規劃問題基本上都存在一個序列,序列中的元素可以按照某種順序唯一地排序.更抽象地說,排序的方式通常是這些元素在某個事件發生過程的時間軸上的位置.
我們說”序列”而非”集合”,是因為元素的唯一排序方式是非常重要的.因為這樣我們就可以將精力集中於組合而非排列.在我看來,動態規劃問題在本質上是一種組合問題,這樣的問題總是可以通過列舉序列元素的全部組合而得以解決.
問題中的序列有時很明顯,有時不明顯(2059),有時是二維的(2571),甚至乾脆沒有顯示給出(2084).不管怎樣,我認為解決動態規劃問題的首要任務就是明確這個序列.序列明確以後,我們就可以直接”腰斬”這個問題並考慮其”下半身”,即直接從序列的第i個元素開始思考問題.
2 問題的表示
由於動態規劃問題總是關聯著一個序列,因此我們可以用序列表示這個問題.比如用[0:]表示原問題,用[i:]表示從問題的”下半身”.就像在物理中我們總是將物體抽象成一個質點一樣,將動態規劃問題抽象成一個序列對我們進一步思考問題有很大的幫助.
當然,沒必要煞有介事地用上[i:]這個類似於切片的語法,直接用數字0表示原問題也未嘗不可–這裡的關鍵是內容,而非形式.我沒有正式地對符號進行定義,因為接下來的討論大多是描述性的,嚴格定義符號顯得過分隆重.
另一方面,用[0:]這樣的形式表述問題是不全面的,因為很多問題的完整描述需要額外的變數.比如對於完全揹包問題,當序列是[0:]時,我們需要額外的一個變數w
這樣,對於一個問題,我們既可以將其表示成[i:]也可以表示成(i,j).儘管符號的使用非常隨意,但是引入一些符號確實能幫助我們更方便地分析問題.下面是我在分析問題時使用的一些符號.
符號 | 意義 |
---|---|
a[i] | 序列的第i個元素 |
[i:] | 從i號元素開始的序列,或者與這個序列相關的問題 |
(i) | 思考問題[i:]時需要列舉的集合,或者第i個決策階段,詳見後面 |
(i,j) |
問題[i:]的另一種表述方式,此時我們強調錶述問題的額外變數j,k等 |
ti,j[x] | 問題(i,j)在決策x下的區域性最優解,詳見後面 |
s[i,j] | 問題(i,j)的解 |
無論如何,就像我剛才說的,這裡重要的是內容而非形式.
3 找到序列
大多數情況下,找到問題的序列是容易的.事實上,我們幾乎總是可以憑藉直覺準確地找到決定問題的那個序列.在一些罕見的直覺失效的情況下,我們可以用下面這種方式來尋找這個序列.
第一種方式是將問題視為一個列舉問題,我們只要列舉某些元素就能夠解決問題.在這種情況下,被列舉的元素構成這個序列.2059就是典型的這種問題,烏龜只要從所有的充電站中選擇合適的若干個來充電,就能在最短的時間內跑到終點.因此,它只要列舉所有的選擇方案就能解決這個問題.被列舉的物件是充電站的組合,那麼充電站的序列自然而然的成為了與這個問題關聯的序列.
第二種方式是將問題視為一個分階段的決策問題,每個階段都關聯著問題中的一個元素,此時,所有階段的所有元素構成與這個問題相關的序列.完全揹包問題是一個典型的決策類動態規劃問題.在第i個階段,我要選擇j個i號物品,然後進入第i+1個階段,進行i+1號物品的選擇.這樣物品的編號就是與這個動態規劃問題關聯的序列.
事實上,我們將會看到,將動態規劃問題視為列舉或決策問題的思想對於解決整個動態規劃問題都十分有效,我們僅僅使用這種思想來尋找問題的序列有點”殺雞用牛刀”的感覺.
無論如何,現在我們擁有一個序列了.接下來我們要面臨真正的考驗,即解決問題[i:].正如我剛才所說,思考這個問題的切入點正是將問題視為一個列舉或決策問題.
4 列舉問題
有些問題可以被非常自然地理解為一個列舉問題.此時,我們將為了解決問題[0:]而需要列舉的集合記為(0),這樣我們的從集合(i)開始思考問題.有兩種思考問題的方式.不管採用哪種方式,我們的目的都是為了用集合(i+1)將集合(i)表示出來.
第一種方式是直接用序列中的元素將集合(i)表示出來,然後重複同樣的步驟以得到集合(i+1).接下來,只要觀察這兩個集合,就可以找到它們的關係.1003是典型的這種問題.
第二種方式採用了歸納法的思想.在這種情況下,假設對於所有的整數x>0,集合(i+x)已知,然後我們想方設法用(i+x)將(i)表示出來.
無論用那種方式,最終我們會得到(i)和(i+x)的表示式.通常是下面這個樣子:
(i) = AUBUC…
其中A,B,C是由(i+x)匯出的集合.
這樣,我們就可以通過考察(i)和(i+x)的關係來找到s[i]和s[i+x]的關係.當然,s[i]和s[i+x]並不是總是存在關係,甚至,(i)並非總是能夠用(i+x)表示.但是對於動態規劃問題,我們有充分的理由來保持樂觀的態度.
5 分階段決策問題
另一些問題很容易被視為一個分階段決策問題,典型的如完全揹包問題,以及1176,2084,2571等.這個時候我們對[i:]的思考非常直接.
從階段(i)開始,我們做出一個決策x,然後進入階段(i+1),即問題[i:]在決策x下變成了問題[i+1:].這個時候,我們使用強調全部變數的方式來表述問題,因此這個變化可以記為:
(i,j) -x-> (i+1, j*)
觀察這個變化,我們就可以進一步找出ti,j[x]與s[i+1,j*]的關係,通常可以表示成下列形式:
ti,j[x] = f(a[i], s[i+1, j*])
問題(i,j)的解可以通過考察所有的ti,j[x]得到:
s[i,j] = g(ti,j[x1], ti,j[x2],…)
注意,做出決策x未必一定會跳轉到階段(i+1),比如跳棋問題,以及1260等.只不過,無論跳轉到(i+1)還是(i+n),思考問題的方式是一樣的,而(i+1)是普遍情況並且更容易表示和理解,因此我使用(i+1)而非更一般的(i+x).
6 補充說明
關於列舉和決策問題,有必要做出以下兩點補充:
第一,列舉問題和分階段決策問題的界線是模糊的.我們既可以強行以列舉的方式解決決策問題,也可以強行以決策的方式解決列舉問題.只是,將有的問題視為列舉問題確實比把它視為決策問題要自然一些,反之也是如此.
第二,解決決策問題比解決列舉問題要容易地多.事實上,當找到集合(i)和(i+x)的關係以後,接下來將集合結構向決策結構靠攏是一個十分有效的思路.
7 什麼樣的問題不是動態規劃問題
似乎我們有一些手段可以判定一個問題不是動態規劃問題,至少,下面這兩類問題看上去很難歸結為動態規劃問題:
- 不能用列舉的方式解決的問題,例如1209和1257;
- 要列舉所有排列才能解決的問題.如2544和1789.
特別地,考慮到動態規劃問題是一些組合問題,即這類問題的本質是從整體中選擇若干個元素.因此如果問題的解一定由全部元素構成(比如問題的解是全部元素的某個排列),那麼這類問題不太可能是一個DP問題.
雖然2544作為最短路徑問題確實有動態規劃解法,但是我傾向於將這個問題劃歸為非DP問題,原因如下:
- 2544的DP解法在本質上與dijkstra演算法是一樣的,而dijkstra演算法通常被視為貪心演算法;
- 2544不具備動態規劃問題的一些常見特徵,如序列特徵(你無法將圖中的節點排成一個唯一序列),組合特徵(相同節點的不同排列構成不同的路徑).
其次,如果如果全排列的個數比較少(通常少於16!),那麼第二類問題有時候可以用動態規劃來解,事實上這是動態規劃問題的一大子類,叫做狀態壓縮動態規劃.
8 不足之處
上述討論只是非常粗略,抽象,淺顯地討論瞭解決動態規劃問題的一些思考方式.算是對上述28個問題的一些高度很底的總結.但是這些總結的直接作用是非常有限的.事實上,上述總結甚至不能指導你完整地解決完全揹包問題和狀態壓縮問題.
不僅如此,有很大一部分動態規劃問題具有巨大的特殊性,以至於它們幾乎完全與上述總結格格不入.如最長公共子序列問題(1159).
而另一部分動態規劃問題雖然可以納入上述思路,但是其最優子結構或者重複子問題是如此難以尋找,以至於我們即使完全照搬上述思路也依然還是會陷入束手無策的境地,例如1421,這是一個最優子結構很難尋找的問題.更進一步,如果不是已經被模型化了,那麼01揹包問題和完全被揹包問題完全可以成為重複子問題難以尋找的典型.
9 後記
正如我剛才所說,每個動態規劃問題都有其特殊性,或者很難找到最優子結構,或者很難找到重複子問題.我想,也許只有數學家才能給出一種簡潔優雅的表示方法,但那必是極度抽象的.而理論越抽象往往越難應用到實際.所以我從不奢求能對動態規劃問題總結出一種”大統一”的東西.
上面這些所謂的”總結”,一非正式,二非全面,三非高人之筆,對一些人而言,說是毫無價值也不為過.但是我的初衷僅僅是希望將來自己看到這篇文章時,能很快回憶起我此時此刻對動態規劃的理解.當然,如果居然有人能夠”不厭其煩”地讀完,並由此而產生一些自己的想法,那我簡直要榮幸至極了.
10 附錄
10.1 統計
DP | 非DP | 補充 | 合計 |
---|---|---|---|
23 | 4 | 1 | 27 |
10.2 題表
動態規劃
# | 分類 | 遍歷物件 | 遍歷方式 | 模型 | 特點 |
---|---|---|---|---|---|
1003 | 決策 | 連續子序列 | 特有的決策方式 | 最大連續子序列和問題 | |
1024 | |||||
1069 | 列舉 | 有序子序列 | 跳轉 | 跳棋問題 | |
1074 | 決策 | 排列 | 有狀態DFS | ||
1087 | 列舉 | 有序子序列 | 跳轉 | 跳棋問題 | |
1114 | 決策 | 數量組合 | 乘法原理 | 完全揹包問題 | |
1159 | 特殊 | 子序列 | 特殊 | 最長公共子序列問題 | |
1160 | 列舉 | 有序子序列 | 特轉 | 跳棋問題 | |
1171 | 決策 | 數量組合 | 乘法原理 | 完全揹包問題 | |
1176 | 決策 | 組合3 | 無狀態DFS | ||
1203 | 決策 | 基本組合 | 無狀態DFS | 01揹包問題 | |
1231 | 決策 | 連續子序列 | 特有的決策方式 | 最大連續子序列和問題 | |
1260 | 決策 | 基本組合 | 無狀態DFS | ||
1284 | 決策 | 數量組合 | 乘法原理 | 完全揹包問題 | |
1421 | |||||
1978 | 決策 | 有序子序列 | 跳轉 | 二維問題 | |
2059 | 決策 | 基本組合 | 無狀態DFS | ||
2084 | 決策 | 基本組合 | 無狀態DFS | ||
2159 | 決策 | 數量組合 | 無狀態DFS | 完全揹包問題 | 多限制,三維 |
2191 | 決策 | 數量組合 | 無狀態DFS | 完全揹包問題 | |
2571 | 決策 | 有序子序列 | 跳轉 | ||
2602 | 決策 | 基本組合 | 無狀態DFS | 01揹包問題 | 教科書式揹包問題 |
2709 | 決策 | 數量組合 | 無狀態DFS | 完全揹包問題 |
補充
# | 分類 | 遍歷物件 | 遍歷方式 | 模型 | 特點 |
---|---|---|---|---|---|
補充 | 集合 | m元組 | 選/不選 |
非動態規劃
# | 模型 | 特點 |
---|---|---|
1029 | 非DP | 非遍歷可解 |
1257 | 非DP | 非遍歷可解 |
1789 | 非DP,貪心 | 排列 |
2544 | 非DP,最短路徑問題 | 排列 |