是的沒錯,這個文章能讓你徹底學會:
dynamic programming 動態規劃
Content
1. 從乘方和諺語提及
2. 斐波那契數列:遞歸受到了挑戰
3. 動態規劃的原理
--3.1最優子結構
--3.2重疊子問題
4.動態規劃的應用
--4.1揹包模型
--4.1.1 零一揹包
--4.1.2 徹底揹包
--4.1.3 多重揹包
--4.2線性動規
--4.3區間動規
5.入土
提示:若是你對動規已經有所瞭解,請直接進入3或4
1.從乘方和諺語提及
相信大家都學習過乘方,那麼咱們知道:
2 X 2 X 2 X 2 X 2 X 2 X 2 X 2 X 2 X 2 =2^10=1024
那麼2^11是多少呢?你會天然而然地答出2048!
好的,祝賀你已經學會了動規初步!
爲何?
咱們做爲人,計算2^11天然不是2X2……X2=2048這樣算的
而是經過已經給出的2^10=1024計算的:2X1024=2048。
這個過程就包括了動規的核心思想之一:
記住子問題的解。
一句諺語解釋:
Those who cannot remember the past, are doomed to repeat it.--George Santayana
那些忘記過去的人註定要重蹈覆轍。
在編程中,動態規劃經過記住子問題的解,可以以高效率低耗時而脫穎而出,成爲最熱門的算法之一(詳見2)。
動規的概念能夠自行百度,在這裏不作過多討論。
2. 斐波那契數列:遞歸受到了挑戰
對於斐波那契數列的求解,遞歸每每是最早考慮的解法,可是在1000ms的苛求下,遞歸O(2^n)的時間複雜度就比較麻煩了。
//遞歸解斐波那契 long long Fib(long long n) { if (n==1||n==2) return 1; else return Fib(n - 1) + Fib(n - 2); }
圖解👇

從圖中能夠看到,光是算fib(6),就要從新算3遍fib(3),時間複雜度就比較噁心了。對了,你想起什麼沒有?記住子問題的答案--動規登場!
1.子問題記憶法
使用一個數組來記錄各個子問題的解,當再一次遇到這一問題的時候直接查找數組來得到解避免屢次計算子問題。
//動規解決斐波那契(子問題記憶法) int fib(int a[],int n) { if (n == 0) { a[0] = 0; return 0; } if (n == 1) { a[1] = 1; return 1; } if (a[n] >= 0) { return a[n]; } a[n] = fib(a, n - 1) + fib(a, n - 2); return fib(a, n - 1) + fib(a, n - 2); }
二、自底向上解決方案
先求解子問題再根據子問題的解來求解父問題,斐波那契數列的子問題圖以下:
//動規解決斐波那契(自底向上解決) int fib(int a[],int n) { a[0] = 0; a[1] = 1; for (int i = 2; i <= n; i++) { a[i] = a[i - 1] + a[i - 2]; } return a[n]; }
自底向上的計算方法實現起來很是容易,分析算法,僅從形式上面分析算法可知,算法的時間主要消耗在計算數據規模爲n的數組裏面的數上面了,因此時間複雜度僅爲:O(n)。遞歸在斐波那契的地位不保了!!
可是咱們能夠看到,在使用動規的時候,咱們彷佛多開了一個數組,那麼空間是否會受到影響?答案是確定的!可是在1000ms,256Mb的條件下,犧牲空間換取時間絕對是一筆很值的交♂易!
3.動態規劃的原理
最優子結構
用動態規劃求解最優化問題的第一步就是刻畫最優解的結構,若是一個問題的解結構包含其子問題的最優解,就稱此問題具備最優子結構性質。所以,某個問題是否適合應用動態規劃算法,它是否具備最優子結構性質是一個很好的線索。使用動態規劃算法時,用子問題的最優解來構造原問題的最優解。所以必須考查最優解中用到的全部子問題。
重疊子問題
在斐波拉契和鋼條切割中,能夠看到大量的重疊子問題,好比說在求fib(6)的時候,fib(2)被調用了5次。若是使用遞歸算法的時候會反覆求解相同的子問題,不停的調用函數,而不是生成新的子問題。若是遞歸算法反覆求解相同的子問題,就稱爲具備重疊子問題性質。在動態規劃算法中使用數組來保存子問題的解,問題屢次求解的時候能夠直接查表不用調用函數遞歸。
簡單來講,動規就是遞歸再加上記錄。
花小部分空間,省大部分時間。
4.動態規劃的應用
4.1揹包問題
1.零一揹包
有N件物品和一個容量爲V的揹包。第i件物品的體積是w[i],價值是c[i]。求解將哪些物品裝入揹包可以使這些物品的費用總和不超過揹包容量,每一個物品只能選用一次,使得價值總和最大。
這是\color{blue}\text{最基礎}最基礎的揹包問題,總的來講就是:選仍是不選
顯然,這道題並不能用貪心,由於假設你要在100時間內採藥,有三個藥:71時間,89價值;30時間,44價值;40時間,45價值。若是是貪心,就會果斷選(71,89),但事實上(30,44)和(40,45)更優。此時請出動規,怎麼用呢?
列出狀態轉移方程!
狀態轉移方程的概念能夠自行百度,在這裏不作過多討論。
對於一個物品,只有兩種狀況
1.第i件不放進去,這時所得價值爲:dp[i-1][v]
2.第i件放進去,這時所得價值爲:dp[i-1][v-c[i]]+w[i]
故狀態轉移方程爲:
dp[i][v] = max(dp[i-1][v], dp[i-1][v-w[i]]+c[i])
注:零一揹包能夠用一維數組記錄最優計劃dp[v],表示不超過v體積的最大價值。
巨佬們看過來!!揹包退火👈瞭解一下?
2.徹底揹包
有N種物品和一個容量爲V的揹包,每種物品都有無限件可用。第i種物品的體積是w[i],價值是c[i]。求解將哪些物品裝入揹包可以使這些物品的費用總和不超過揹包容量,且價值總和最大。
徹底揹包和01揹包十分相像, 區別就是徹底揹包物品有無限件。總的來講就是選仍是不選,選幾件。
列出狀態轉移方程!
若不採這種草藥,則時間花費沒有增多,通過的 草藥種數增長了1,採到草藥價格不變,因此 dp[i][j]=dp[i-1][j];
若採這種草藥,則 時間花費增長了t[i],種數增長1,採到草藥價格增長了p[i],因此 dp[i][j]=dp[i-1][j-t[i]]+p[i]。
咱們要使dp[i][j]\color{red}\text{儘量大}儘量大,即👇
dp[i][j]=max(dp[i-1][j],f[i-1][j-t[i]]+p[i])
這道題對於藥就沒有限制了,每種都無限多,因此地主家的傻孩子優化出現了:
當一個藥品的價值小於另外一個藥品的價值,而且時間高於另外一個藥品,咱們就能夠不去考慮這個藥品。既然咱們不是地主家的傻孩子,爲何還要花更多的時間採更少價值的藥呢😂?
注:該問題也能夠壓縮到一維最優計劃dp[v],咱們能夠將藥的種類i省略 ,用後面算的值覆蓋掉前面算的值.
巨佬們,揹包退火👈瞭解一下?
3.多重揹包
有N種物品和一個容量爲V的揹包。第i種物品有n[i]個可用,每件費用是w[i],價值是c[i]。求解將哪些物品裝入揹包可以使這些物品的費用總和不超過揹包容量,且價值總和最大。
這裏又多了一個限制條件,每一個物品規定了可用的次數。
我纔不會告訴你我如今仍是把多重揹包拆成01揹包來作的呢
沒錯,對於通常的多重揹包,換成01揹包水就好了,至於不通常的嘛……
列出狀態轉移方程!
f[i][v]=max(f[i-1][v-k*w[i]]+ k*c[i]|0<=k<=n[i])
4.2線性動規
其實線性動規是動規中較簡單的一類題目,重點有三:
1.選對算法,正確維護
2.巧妙利用前綴和
3.別忘了合理遞推
線性遞歸的題我的以爲要多練,因此放了幾道題,但願在刷題的過程當中,你可以領悟其精髓。同時,你能夠參考一個近乎萬能的線性模板博客👉線性動態規劃
4.3區間動規
典型的區間模型,迴文串擁有很明顯的子結構特徵,即當字符串s是一個迴文串時,在X兩邊各添加一個字符'a'後,a s a仍然是一個迴文串,咱們用d[i][j]來表示A[i…j]這個子串變成迴文串所須要添加的最少的字符數,那麼對於A[i] == A[j]的狀況,很明顯有
d[i][j] = d[i+1][j-1]
這裏須要明確一點,當i+1 > j-1時也是有意義的,它表明的是空串,空串也是一個迴文串,因此這種狀況下d[i+1][j-1] = 0;
當A[i] != A[j]時,咱們將它變成更小的子問題求解,咱們有兩種決策:
1.在A[j]後面添加一個字符A[i];
2.在A[i]前面添加一個字符A[j];
列出狀態轉移方程!
d[i][j] = min{ d[i+1][j], d[i][j-1] } + 1; //每次狀態轉移,區間長度增長1
撒花✿✿ヽ(°▽°)ノ✿
誒Σ( ° △ °|||)好像忘了點什麼?
5.入土
如題所述,動態規劃在時間上遠勝遞歸,在通用性上遠勝貪心,可是它畢竟只是個算法,不是自動AC機,接下來咱們簡單的談談它的缺點:
1.它節省時間的代價是浪費空間
這一點算是比較弱的缺點了,由於它有時開的空間只是九牛一毛,不值一提。可是!!在解決二維地圖題中,要開一個dp[10000][10000]就會比較吃力。
2.它須要具體問題具體分析
不像貪心或者遞歸,dp是基本不具備模板的(最長單調子序列模板別吵吵),並且在打dp以前,要仔細思考,由於號稱具備全局最優解的動規有可能讓你失去人生中美好的1.5小時。
3.它的狀態轉移方程須要考慮
再確認要用dp一頭磕死在一道題上以後,必需要作的就是確認子問題之間的關係,而且由題意得出找出全局最優解的狀態轉移方程,若是找不出來,那麼就應該及時更換方法。
4.dp並非不須要優化
向其餘的大部分算法同樣,dp也是須要優化的,可是優化每每又讓人無從下手,搜索能夠剪枝,但dp就不必定行了。好比上面的瘋狂採藥問題,咱們才能就提論題地進行優化。那麼dp有沒有具備廣泛性的優化呢?有的!每每,咱們能夠對dp經過滾動數組省空間、壓縮一維省時間、狀態壓縮二進制、矩陣優化轉問題、線段樹優化區間、樹狀數組O(logn) etc.
寫了一下午,感受寫完本身已經先一步入土惹……
撒花✿✿ヽ(°▽°)ノ✿