1. 程式人生 > 其它 >動態規劃專題[1]: 簡單線性DP

動態規劃專題[1]: 簡單線性DP

本專題文章建立在本人多年寫動態規劃程式碼的經驗上,用以自己回顧總結,也幫助朋友初步理解,部分理解可能和教科書有所出入,要參加演算法考試的同學請以教科書為準。

概念引入

在現實生活中,有一類活動的過程,由於它的特殊性,可將過程分成若干個互相聯絡的階段,在它的每一階段都需要作出決策,從而使整個過程達到最好的活動效果。因此各個階段決策的選取不能任意確定,它依賴於當前面臨的狀態,又影響以後的發展。當各個階段決策確定後,就組成一個決策序列,因而也就確定了整個過程的一條活動路線.這種把一個問題看作是一個前後關聯具有鏈狀結構的多階段過程就稱為多階段決策過程,這種問題稱為多階段決策問題。在多階段決策問題中,各個階段採取的決策,一般來說是與時間有關的,決策依賴於當前狀態,又隨即引起狀態的轉移,一個決策序列就是在變化的狀態中產生出來的,故有“動態”的含義,稱這種解決多階段決策最優化的過程為動態規劃方法[1]。

雖然這段話非常抽象,但它清晰地解釋了動態規劃(Dynamic Programming,下文簡稱DP)為什麼“動態”。包括我在內的部分人認為DP不是一種演算法而是一種方法或者說思想。我們通常認為演算法是程式設計的方法論,那麼DP這類的思想可以說是演算法的方法論,比如著名的floyd多源最短路演算法的核心思想就是DP。

動態規劃首先是一個規劃:給出一個規劃問題,讓求最優解或最優決策,比如說一個老闆有多條生產線,如何分配產能得到最多的剩餘價值,當然這個問題可以用高中學過的線性規劃來解決。而我們為什麼強調它是動態的,是因為它在不同步驟做決策時狀態變了,且通常做的決策不能只顧當前,還要瞻前(DP的前效性)顧後(DP的後效性)。什麼是狀態?可以這樣簡單的理解,工廠各條生產線對應商品的市場價就是狀態,我們做線性規劃的前提是市場價不發生改變,可以規劃出當下最優解。但如果市場價每天都發生變化,也就是說狀態是在變的,那麼這個規劃問題就可以說是個動態規劃問題。

如果看到這你感覺雲裡霧裡的,沒關係,可以在學了幾種DP,寫個十幾道題之後再回頭看概念。

這裡把各個術語的定義[2]丟一下,可以先不看。

  • 階段:把所給求解問題的過程恰當地分成若干個相互聯絡的階段,以便於求解,過程不同,階段數就可能不同.描述階段的變數稱為階段變數。在多數情況下,階段變數是離散的,用k表示。此外,也有階段變數是連續的情形。如果過程可以在任何時刻作出決策,且在任意兩個不同的時刻之間允許有無窮多個決策時,階段變數就是連續的。
  • 狀態:狀態表示每個階段開始面臨的自然狀況或客觀條件,它不以人們的主觀意志為轉移,也稱為不可控因素。在上面的例子中狀態就是某階段的出發位置,它既是該階段某路的起點,同時又是前一階段某支路的終點 。
  • 無後效性:我們要求狀態具有下面的性質:如果給定某一階段的狀態,則在這一階段以後過程的發展不受這階段以前各段狀態的影響,所有各階段都確定時,整個過程也就確定了。換句話說,過程的每一次實現可以用一個狀態序列表示,在前面的例子中每階段的狀態是該線路的始點,確定了這些點的序列,整個線路也就完全確定。從某一階段以後的線路開始,當這段的始點給定時,不受以前線路(所通過的點)的影響。狀態的這個性質意味著過程的歷史只能通過當前的狀態去影響它的未來的發展,這個性質稱為無後效性。
  • 決策:一個階段的狀態給定以後,從該狀態演變到下一階段某個狀態的一種選擇(行動)稱為決策。在最優控制中,也稱為控制。在許多問題中,決策可以自然而然地表示為一個數或一組數。不同的決策對應著不同的數值。描述決策的變數稱決策變數,因狀態滿足無後效性,故在每個階段選擇決策時只需考慮當前的狀態而無須考慮過程的歷史。
  • 策略:由每個階段的決策組成的序列稱為策略。對於每一個實際的多階段決策過程,可供選取的策略有一定的範圍限制,這個範圍稱為允許策略集合。

簡單線性DP

具有線性狀態劃分的動歸稱為線性DP,通常其狀態轉移也是線性的。

數塔問題


原題連結
這道題非常有來頭,是1994年IOI(international olympiad in informatics)的題,當時DP還沒怎麼普及。DP普及之後,這道題在洛谷的難度被標為普及-,也就是還沒達到初中生競賽的難度。
如果不用DP,這道題該怎麼做?搜尋,把所有可以走的路枚舉出來,找到所有路中的最優解。數塔有N層的,總共有幾條路呢?有 \(2^{N-1}\) 條。也就是說演算法的複雜度是 \(O(logn)\) 的。這裡放上DFS的程式碼:

點選檢視程式碼
//Luogu P1216 coded by Jiayou
#include<iostream>
using namespace std;
int N, map[105][105];
int dfs(int floor,int pos){
	//到達第floor層,第pos個數字
	if(floor==N)
		return map[floor][pos];
	else
		return max(dfs(floor + 1, pos), dfs(floor + 1, pos + 1)) + map[floor][pos];
}
int main(){
	cin >> N;
	for (int i = 1; i <= N;i++)
		for (int j = 1; j <= i;j++)
			cin >> map[i][j];
	cout << dfs(1, 1);//從第一層第一個數字出發
}

最長不下降子序列問題

最長公共子序列問題

最長迴文子序列問題

參考資料

[1] 田翠華.演算法設計與分析:冶金工業出版社,2007-08:106
[2] 葉金霞,白春章.資訊科技 九年級:遼寧師範大學出版社,2008-07:113-114