百度百科說:動態規劃是運籌學的一個分支,是求解決策過程最優化的數學方法。20世紀50年代初美國數學家R.E.Bellman等人在研究多階段決策過程的優化問題時,提出了著名的最優化原理,把多階段過程轉化爲一系列單階段問題,利用各階段之間的關係,逐個求解,創立了解決這類過程優化問題的新方法——動態規劃。ios
適用條件算法
任何思想方法都有必定的侷限性,超出了特定條件,它就失去了做用。一樣,動態規劃也並非萬能的。適用動態規劃的問題必須知足最優化原理和無後效性。ide
1.最優化原理(最優子結構性質) 最優化原理可這樣闡述:一個最優化策略具備這樣的性質,不論過去狀態和決策如何,對前面的決策所造成的狀態而言,餘下的諸決策必須構成最優策略。簡而言之,一個最優化策略的子策略老是最優的。一個問題知足最優化原理又稱其具備最優子結構性質。優化
2.無後效性將各階段按照必定的次序排列好以後,對於某個給定的階段狀態,它之前各階段的狀態沒法直接影響它將來的決策,而只能經過當前的這個狀態。換句話說,每一個狀態都是過去歷史的一個完整總結。這就是無後向性,又稱爲無後效性。spa
3.子問題的重疊性 動態規劃將原來具備指數級時間複雜度的搜索算法改進成了具備多項式時間複雜度的算法。其中的關鍵在於解決冗餘,這是動態規劃算法的根本目的。動態規劃實質上是一種以空間換時間的技術,它在實現的過程當中,不得不存儲產生過程當中的各類狀態,因此它的空間複雜度要大於其它的算法。設計
狀態轉移方程,是動態規劃中本階段的狀態每每是上一階段狀態和上一階段決策的結果。若是給定了第K階段的狀態Sk以及決策uk(Sk),則第K+1階段的狀態Sk+1也就徹底肯定。code
一般狀況下,在肯定一個題須要用到動規時,咱們要先肯定出他的狀態轉移方程。根據狀態轉移方程寫出代碼。blog
必定要注意邊界條件。狀態轉移方程要從第二層開始用。ci
來看幾個栗子;get
小浣熊鬆鬆特別喜歡交朋友,今年鬆鬆生日,就有N個朋友給他送禮物。但是要把這些禮物搬回家是一件很困難的事,具體來講,若是鬆鬆一次搬運x件禮物,就要花費w[x]的體力(顯而易見,有w[x]<=w[x+1],搬得越多耗費體力越多)。鬆鬆並不在乎他會搬多少次,可是他想知道,本身最少花費多少體力,就能夠把禮物所有搬回家。
#include<iostream> #include<cstdio> using namespace std; int main() { int a[15111],n; cin>>n; for(int i=1;i<=n;++i) cin>>a[i]; for(int i=1;i<=n;++i) for(int j=1;j<i;++j) a[i]=min(a[i-j]+a[j],a[i]); cout<<a[n]; return 0; }
a[i]記錄運送i件禮物最小體力花費,初值爲輸入值,經過遞推計算出N件禮物最小體力花費。
某國爲了防護敵國的導彈襲擊,發展出一種導彈攔截系統。可是這種導彈攔截系統有一個缺陷:雖然它的第一發炮彈可以到達任意的高度,可是之後每一發炮彈都不能高於前一發的高度。某天,雷達捕捉到敵國的導彈來襲。因爲該系統還在試用階段,因此只有一套系統,所以有可能不能攔截全部的導彈。
#include<iostream> #include<algorithm> using namespace std; int n=1; int a[1005]; int dp[1005]; int xulie1() { for(int i=1;i<=n;i++) for(int j=1;j<i;j++) if(a[j]>a[i]) dp[i]=max(dp[j]+1,dp[i]); int maxl=-1; for(int i=1;i<=n;++i) maxl=max(maxl,dp[i]); return maxl; } int xulie2() { for(int i=1;i<=n;i++) dp[i]=1; for(int i=1;i<=n;i++) for(int j=1;j<i;j++) if(a[j]<=a[i]) dp[i]=max(dp[j]+1,dp[i]); int maxl=-1; for(int i=1;i<=n;++i) maxl=max(maxl,dp[i]); return maxl; } int main() { while(cin>>a[n]) n++; cout<<xulie1()<<endl; cout<<xulie2(); }
例3:codevs 5294 挖地雷傳送門
在一個地圖上有N個地窖(N<=20),每一個地窖中埋有必定數量的地雷。同時,給出地窖之間的鏈接路徑。當地窖及其鏈接的數據給出以後,某人能夠從第一個地窖開始挖地雷,而後能夠沿着指出的鏈接往下挖(僅能選擇一條路徑),當無鏈接時挖地雷工做結束。設計一個挖地雷的方案,使某人能挖到最多的地雷。
思路:因爲能夠從任何一個地窖開始挖,因此要從每一個點開始遞推;
#include<iostream> using namespace std; int s[15111],num[15111]; int n; bool sum[15111][15111]; int path[15111]; int main() { cin>>n; for(int i=1;i<=n;++i) cin>>s[n]; for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) cin>>sum[i][j]; for(int i=n;i>=1;--i) for(int j=i;j<=n;++j) if(sum[i][j]) { if(num[i]<num[j]+s[i]) { num[i]=num[j]+s[i]; path[i]=j; } } else num[i]=max(num[i],s[i]); int maxl=0,k=0; for(int i=1;i<=n;++i) if(num[i]>maxl) { maxl=num[i]; k=i; } int qu=k; while(k!=0) { cout<<k<<' '; k=path[k]; } cout<<endl; cout<<maxl; }
有n堆石子排成一列,每堆石子有一個重量w[i], 每次合併能夠合併相鄰的兩堆石子,一次合併的代價爲兩堆石子的重量和w[i]+w[i+1]。問安排怎樣的合併順序,可以使得總合並代價達到最小。
思路:遞推和記憶化搜索
#include<iostream> #include<cstring> using namespace std; int dp[1005][1005]; int s[1005]; int n; int main() { s[1]=0; cin>>n; for(int i=1;i<=n;++i) { int sum; cin>>sum; s[i]=s[i-1]+sum; } memset(dp,0x3f,sizeof(dp)); for(int i=1;i<=n;++i)dp[i][i]=0; for(int i=n;i>0;--i) for(int j=i;j<=n;++j) for(int k=i;k<j;++k) dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+s[j]-s[i-1]); cout<<dp[1][n]; return 0; }