维护无后效性的技巧——立即计算代价
簡介
無后效性是動態規劃的一個基本特征之一,只有具備了無后效性的問題才可以使用動態規劃求解。直觀上講,無后效性是指“現在不會影響未來”,或者說現在的決策不會影響未來如何決策。一個不具有無后效性的例子是矩陣尋路算法。設想一個0-1矩陣,尋找一條從1,1到n,n的最短路,不能使用下面的記憶化搜索算法:
FIND-PATH(x,y)if (out of matrix) return ∞if (x,y == n,n) return 0return dp[x][y] = min(FIND-PATH(x+1,y), FIND-PATH(x-1,y), FIND-PATH(x,y+1), FIND-PATH(x,y-1) )/*例如 1 0 0 0 1 0 1 1 1 1 1 1 1 0 0 1 這個算法會陷入無限遞歸 */為什么這個算法無法正確結束呢?不難發現,這個算法并沒有在決策(x,y)后要求以后不再遞歸計算(x,y),這就導致遞歸計算(x,y)的前提包括求解(x,y),算法會一直執著于遞歸計算(x,y)而無法正確結束。
不難想到增加一個輔助數組now[][] = {0}。沒要計算一個(x,y)的時候就now[x][y] = 1防止落入死循環。
顯然這個方法是正確的。那么能否直接加入dp[][]實現記憶化搜索呢?不行!考慮記憶化搜索(dp)的原理——無論何時計算dfs(x,y),得出的結果都是同一個值,因此不必從新計算。然而由于輔助數組now的加入,導致由于now的不同,不同時候詢問dfs(x,y)的結果可能不同。我們便稱這個問題違背無后效性原則,更準確的,這個問題的子問題圖存在環。
很多dp問題的無后效性都是顯然的。然而一些時候,無后效性需要通過一些方法來維護。下面用幾個例子簡單分析如何維護無后效性。
最優二分檢索樹
- 給定N個單調增數據a1..aN的權值f1..fN,構造一棵二分檢索樹,ai的深度記作di,使得代價sum{di*fi}最小。
很顯然的區間dp題目,在區間i..j中枚舉一個k,遞歸計算i..k-1的代價和k+1..j的代價,再加上k的代價即可。然而深度的計算遇到了麻煩。每一次試圖將序列分成兩部分時,會使左右子樹每一個數據深度+1,這會影響到之后的決策。也就是說,對于同一個區間i..j來說,由于所處的深度不同,結果也不同。違背無后效性。
一個顯然的思路是更改狀態,用dp[i][j][d]表示i..j深度為d時的代價。但是顯然這個方法復雜度為Θ(n^4),難以接受。
狀態不能更改,不如考慮決策。未進行操作的序列每一個元素深度為0;每對i..j進行一次決策,會導致其間的所有元素深度加一——這就是違背無后效性的一點。為了消除后效,我們必須一次性結算這次決策造成之后決策改變的總量。更直觀地,我們將di*fi看作fi連續加法,即 fi+fi+fi...di個fi,每進行一個決策,當前區間內每一個元素ai代價都會加上fi,也就是當前區間所有元素的代價和。dp方程如下:
用記憶化搜索實現即可,注意處理邊界。復雜度Θ(n^3)。據說可以優化到Θ(n^2)不過我不會。
刪數 ——tyvj
- 對于一個數列a1..an,每次從左面和右面刪除一個數,第i次刪去aj的代價是i*aj,求將數列全部刪除的最小代價。
和上一個題目有異曲同工之妙。這里不再分析為何需要維護無后效性,直接給出維護方法。
不妨稱題目中的i為一個元素aj的操作時間。每一次決策(刪除一個數),會導致剩下的所有數的操作時間加一;如果把代價i(j)*aj看作連續加法aj+aj+aj...i(j)個aj,每一次i加一會導致每一個未刪去元素aj的代價增加aj。dp[i,j]表示刪去ai..aj的最小代價,則有方程:
任務安排——tyvj
N個任務排成一個序列在一臺機器上等待完成(順序不得改變),這N個任務被分成若干批,每批包含相鄰的若干任務。從時刻0開始,這些任務被分批加工,第i個任務單獨完成所需的時間是Ti。在每批任務開始前,機器需要啟動時間S,而完成這批任務所需的時間是各個任務需要時間的總和(同一批任務將在同一時刻完成)。每個任務的費用是它的完成時刻乘以一個費用系數Fi。請確定一個分組方案,使得總費用最小。
例如:S=1;T={1,3,4,2,1};F={3,2,3,3,4}。如果分組方案是{1,2}、{3}、{4,5},則完成時間分別為{5,5,10,14,14},費用C={15,10,30,42,56},總費用就是153。
輸入格式
- 第一行是N(1<=N<=5000)。
- 第二行是S(0<=S<=50)。
- 下面N行每行有一對數,分別為Ti和Fi,均為不大于100的正整數,表示第i個任務單獨完成所需的時間是Ti及其費用系數Fi。
輸出格式
- 一個數,最小的總費用。
測試樣例
- 輸入
- 輸出
很顯然,由于順序不能改變,所以可以使用dp來求解。狀態是關鍵! .
雖然時間就是金錢,但是這里我們會把F值看作金錢,而時間看成金錢的單位。
我們仍然考慮每當一個決策可能在未來產生消費時,就立刻預先支付這個價值從而維護無后效性。
分析問題: 每個任務的費用是它的完成時刻乘以一個費用系數Fi。不妨看成費用系數Fi進行了連續加法,每當當前決策之前的決策每使時間過去了1,就要在當前決策的費用中加上一個Fi;反過來,當前的決策每使時間增加k分鐘,或者如開頭所說收了k次錢,就會使它以及其后的每一個任務的費用加上Fi。由此得出dp方程:
深刻理解這個方程,會受益匪淺。
總結
從以上三例可以找出一個共同點——都是有乘法的區間dp!事實上,乘法看成加法是立即計算代價一種很自然的方式。正是通過直接將決策造成的所有后效性直接計算出來, 使得不需要考慮是否需要為以前的決策買單。 立即計算代價無疑是一種省心的方法。
參考資料
《算法藝術與信息學競賽》
《算法導論》
部分網上內容,http://www.tyvj.cn 題解
轉載于:https://www.cnblogs.com/ljt12138/p/6684391.html
總結
以上是生活随笔為你收集整理的维护无后效性的技巧——立即计算代价的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 迷宫寻宝(一) ---- 状
- 下一篇: Android学习笔记ListView