日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

动态规划思想

發(fā)布時(shí)間:2025/3/20 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 动态规划思想 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

原文:http://blog.csdn.net/u013445530/article/details/45645307#

什么是動(dòng)態(tài)規(guī)劃 ?

動(dòng)態(tài)規(guī)劃( D ynamic P rogramming ,所以我們簡(jiǎn)稱動(dòng)態(tài)規(guī)劃為 DP )是 運(yùn)籌學(xué) 的一個(gè)分支,是求解決策過(guò)程(decision process) 最優(yōu)化的數(shù)學(xué)方法。 20 世紀(jì) 50 年代初 美國(guó) 數(shù)學(xué)家R.E.Bellman 等人在研究多階段決策過(guò)程 (multistep decision process) 的優(yōu)化問(wèn)題時(shí),提出了著名的最優(yōu)化原理 (principle of optimality) ,把多階段過(guò)程轉(zhuǎn)化為一系列單階段問(wèn)題,利用各階段之間的關(guān)系,逐個(gè)求解,創(chuàng)立了解決這類過(guò)程優(yōu)化問(wèn)題的新方法 —— 動(dòng)態(tài)規(guī)劃。 1957 年出版了他的名著《 Dynamic Programming 》,這是該領(lǐng)域的第一本著作。
動(dòng)態(tài)規(guī)劃算法通常基于一個(gè)遞推公式及一個(gè)或多個(gè)初始狀態(tài)。當(dāng)前子問(wèn)題的解將由上一次子問(wèn)題的解推出。使用動(dòng)態(tài)規(guī)劃來(lái)解題只需要多項(xiàng)式時(shí)間復(fù)雜度,因此它比回溯法、暴力法等要快許多。
說(shuō)了這么多術(shù)語(yǔ),想必大家都很頭疼, 現(xiàn)在讓我們通過(guò)一個(gè)例子來(lái)了解一下DP 的基本原理。

首先,我們要找到某個(gè)狀態(tài)的最優(yōu)解,然后在它的幫助下,找到下一個(gè)狀態(tài)的最優(yōu)解。?

這句話暫時(shí)理解不了沒(méi)關(guān)系,請(qǐng)看下面的例子 :

硬幣問(wèn)題:

如果我們有面值為1 元、 3 元和 5 元的硬幣若干枚,如何用最少的硬幣湊夠 11 元?我們憑直觀感覺(jué)告訴自己,先選面值最大,因此最多選 2枚 5 元的硬幣,現(xiàn)在是 10 元了,還差一元,接下來(lái)我們挑選第二大的 3 元硬幣,發(fā)現(xiàn)不行( 10+3=13 超了),因此我們繼續(xù)選第三大的硬幣也就是 1 元硬幣,選一個(gè)就可以( 10+1=11 ),所以總共用了 3 枚硬幣湊夠了 11 元。這就是貪心法,每次選最大的。但是我們將面值改為 2 元, 3 元和 5 元的硬幣,再用貪心法就不行了。為什么呢?按照貪心思路,我們同樣先取 2 枚最大 5 元硬幣,現(xiàn)在 10 元了,還差一元,接下來(lái)選第二大的,發(fā)現(xiàn)不行,再選第三大的,還是不行,這時(shí)用貪心方法永遠(yuǎn)湊不出 11 元,但是你仔細(xì)看看,其實(shí)我們可以湊出 11 元的, 2 枚 3 元硬幣和 1 枚五元硬幣就行了,這是人經(jīng)過(guò)思考判斷出來(lái)了的,但是怎么讓計(jì)算機(jī)算出來(lái)呢?

這就要用動(dòng)態(tài)規(guī)劃的思想:

首先我們思考一個(gè)問(wèn)題,如何用最少的硬幣湊夠i 元 (i<11) ?為什么要這么問(wèn)呢?兩個(gè)原因: 1. 當(dāng)我們遇到一個(gè)大問(wèn)題時(shí),總是習(xí)慣把問(wèn)題的規(guī)模變小,這樣便于分析討論。 2. 這個(gè)規(guī)模變小后的問(wèn)題和原來(lái)的問(wèn)題是同質(zhì)的,除了規(guī)模變小,其它的都是一樣的,本質(zhì)上它還是同一個(gè)問(wèn)題 ( 規(guī)模變小后的問(wèn)題其實(shí)是原問(wèn)題的子問(wèn)題 )
好了,讓我們從最小的i 開(kāi)始吧。當(dāng) i=0 ,即我們需要多少個(gè)硬幣來(lái)湊夠 0 元。由于 1 , 3 , 5 都大于 0 ,即沒(méi)有比 0 小的幣值,因此湊夠 0 元我們最少需要 0 個(gè)硬幣。 ( 這個(gè)分析很傻是不是?別著急,這個(gè)思路有利于我們理清動(dòng)態(tài)規(guī)劃究竟在做些什么。 ) 這時(shí)候我們發(fā)現(xiàn)用一個(gè)標(biāo)記來(lái)表示這句 “ 湊夠 0 元我們最少需要 0 個(gè)硬幣。 ” 會(huì)比較方便,如果一直用純文字來(lái)表述,不出一會(huì)兒你就會(huì)覺(jué)得很繞了。那么,我們用 d(i)=j 來(lái)表示湊夠 i 元最少需要 j 個(gè)硬幣。于是我們已經(jīng)得到了 d(0)=0 ,表示湊夠 0 元最小需要 0 個(gè)硬幣。當(dāng) i=1 時(shí),只有面值為 1 元的硬幣可用,因此我們拿起一個(gè)面值為 1 的硬幣,接下來(lái)只需要湊夠 0 元即可,而這個(gè)是已經(jīng)知道答案的,即 d(0)=0 。所以, d(1)=d(1-1)+1=d(0)+1=0+1=1 。當(dāng) i=2 時(shí),仍然只有面值為 1 的硬幣可用,于是我拿起一個(gè)面值為 1 的硬幣,接下來(lái)我只需要再湊夠 2-1=1 元即可 ( 記得要用最小的硬幣數(shù)量 ) ,而這個(gè)答案也已經(jīng)知道了。所以 d(2)=d(2-1)+1=d(1)+1=1+1=2 。一直到這里,你都可能會(huì)覺(jué)得,好無(wú)聊,感覺(jué)像做小學(xué)生的題目似的。因?yàn)槲覀円恢倍贾荒懿僮髅嬷禐?1 的硬幣!耐心點(diǎn),讓我們看看 i=3 時(shí)的情況。當(dāng) i=3 時(shí),我們能用的硬幣就有兩種了: 1 元的和 3 元的 ( 5 元的仍然沒(méi)用,因?yàn)槟阈枰獪惖臄?shù)目是 3 元! 5 元太多了親 ) 。既然能用的硬幣有兩種,我就有兩種方案。如果我拿了一個(gè) 1 元的硬幣,我的目標(biāo)就變?yōu)榱?#xff1a;湊夠 3-1=2 元需要的最少硬幣數(shù)量。即 d(3)=d(3-1)+1=d(2)+1=2+1=3 。這個(gè)方案說(shuō)的是,我拿 3 個(gè) 1 元的硬幣;第二種方案是我拿起一個(gè) 3 元的硬幣,我的目標(biāo)就變成:湊夠 3-3=0 元需要的最少硬幣數(shù)量。即 d(3)=d(3-3)+1=d(0)+1=0+1=1. 這個(gè)方案說(shuō)的是,我拿 1 個(gè) 3 元的硬幣。好了,這兩種方案哪種更優(yōu)呢?記得我們可是要用最少的硬幣數(shù)量來(lái)湊夠 3 元的。所以,選擇 d(3)=1 ,怎么來(lái)的呢?具體是這樣得到的: d(3)=min{d(3-1)+1, d(3-3)+1} 。
OK,碼了這么多字講具體的東西,讓我們來(lái)點(diǎn)抽象的。從以上的文字中,我們要抽出動(dòng)態(tài)規(guī)劃里非常重要的兩個(gè)概念:狀態(tài)和狀態(tài)轉(zhuǎn)移方程。
上文中d(i) 表示湊夠 i 元需要的最少硬幣數(shù)量,我們將它定義為該問(wèn)題的 " 狀態(tài) " ,這個(gè)狀態(tài)是怎么找出來(lái)的呢?根據(jù)子問(wèn)題定義狀態(tài)。你找到子問(wèn)題,狀態(tài)也就浮出水面了。最終我們要求解的問(wèn)題,可以用這個(gè)狀態(tài)來(lái)表示: d(11) ,即湊夠 11 元最少需要多少個(gè)硬幣。那狀態(tài)轉(zhuǎn)移方程是什么呢?既然我們用 d(i) 表示狀態(tài),那么狀態(tài)轉(zhuǎn)移方程自然包含 d(i) ,上文中包含狀態(tài) d(i) 的方程是: d(3)=min{d(3-1)+1, d(3-3)+1} 。沒(méi)錯(cuò),它就是狀態(tài)轉(zhuǎn)移方程,描述狀態(tài)之間是如何轉(zhuǎn)移的。當(dāng)然,我們要對(duì)它抽象一下,
d(i)=min{ d(i-v j )+1 },其中 i-v j >=0, v j 表示第j 個(gè)硬幣的面值 ;

有了狀態(tài)和狀態(tài)轉(zhuǎn)移方程,這個(gè)問(wèn)題基本上也就解決了。當(dāng)然了,Talk is cheap,show me the code!

int main() {int a[3] = {1,3,5},sum = 11,cent = 0,dp[12];dp[0] = 0;for(int i = 1; i <= sum; i++) dp[i] = i;//我們假設(shè)存在1元的硬幣那么i元最多只需要i枚1元硬幣,當(dāng)然最好設(shè)置dp[i]等于無(wú)窮大for(int i = 1; i <= sum; i++){for(int j = 0; j < 3; j++){if(i >= a[j] && dp[i - a[j]] + 1 < dp[i]){dp[i] = dp[i- a[j] ] + 1;}}}cout<<dp[sum]<<endl;return 0; }從上圖可以得出,要湊夠11元至少需要3枚硬幣。
此外,通過(guò)追蹤我們是如何從前一個(gè)狀態(tài)值得到當(dāng)前狀態(tài)值的,可以找到每一次我們用的是什么面值的硬幣。比如,從上面的圖我們可以看出,最終結(jié)果d(11)=d(10)+1(面值為1),而d(10)=d(5)+1(面值為5),最后d(5)=d(0)+1 (面值為5)。所以我們湊夠11元最少需要的3枚硬幣是:1元、5元、5元。

通過(guò)硬幣問(wèn)題我們初識(shí)DP的原理,其實(shí)可以說(shuō)貪心問(wèn)題是DP問(wèn)題的特例,現(xiàn)在我們通過(guò)幾道題目加深對(duì)DP問(wèn)題的理解

數(shù)塔問(wèn)題:

數(shù)塔問(wèn)題是動(dòng)態(tài)規(guī)劃經(jīng)典的題目,下面來(lái)初步講解下
將一個(gè)由N行數(shù)字組成的三角形,如圖所以,設(shè)計(jì)一個(gè)算法,計(jì)算出三角形的由頂至底的一條路徑,使該路徑經(jīng)過(guò)的數(shù)字總和最大。
學(xué)弟學(xué)妹們你們之前學(xué)過(guò)DFS和BFS,第一眼看過(guò)去這題應(yīng)該用DFS解決,沒(méi)錯(cuò),DFS也可以,但是我們觀察下n行總共有(1 + 2 + 3 + 4+...+n) = (1+n)*n/2個(gè)節(jié)點(diǎn),在遞歸求解的過(guò)程中很多節(jié)點(diǎn)被重復(fù)訪問(wèn)了,這就導(dǎo)致時(shí)間大大增加,必然超時(shí)

但是如果用DP的話這個(gè)節(jié)點(diǎn)可以只訪問(wèn)一次


好了,現(xiàn)在我們用DP解決這道問(wèn)題


將上圖轉(zhuǎn)化一下:


假設(shè)上圖用map[][]數(shù)組保存。
令f[i][j]表示從頂點(diǎn)(1, 1)到頂點(diǎn)(i, j)的最大值。
則可以得到狀態(tài)轉(zhuǎn)移方程:
f[i][j] = max(f[i+1][j], f[i+1][j+1]) + map[i][j]
此題既適合自頂而下的方法做,也適合自底而上的方法,
當(dāng)用自頂而下的方法做時(shí),最后要在在最后一列數(shù)中找出最大值,
而用自底而上的方法做時(shí),f[1][1]即為最大值。
所以我們將圖2根據(jù)狀態(tài)轉(zhuǎn)移方程可以得到圖3:


最大值是30.
代碼如下:

#include <cstdio> #include <iostream> #include <algorithm> #include <cstring> using namespace std; int a[2000][2000]; int main() { int t,n,i,j; while(~scanf("%d",&n)) { for(i=0; i<n; i++) for(j=0; j<=i; j++) scanf("%d",&a[i][j]); for(i=n-1; i>0; i--) for(j=0; j<i; j++) a[i-1][j]+=max(a[i][j],a[i][j+1]); printf("%d\n",a[0][0]); } return 0; } 上面討論了兩個(gè)非常簡(jiǎn)單的例子。現(xiàn)在讓我們來(lái)看看對(duì)于更復(fù)雜的問(wèn)題, 如何找到狀態(tài)之間的轉(zhuǎn)移方式(即找到狀態(tài)轉(zhuǎn)移方程)。為此我們要引入一個(gè)新詞叫遞推關(guān)系來(lái)將狀態(tài)聯(lián)系起來(lái)(說(shuō)的還是狀態(tài)轉(zhuǎn)移方程)
OK,上例子,看看它是如何工作的。

最長(zhǎng)非降子序列:
一個(gè)序列有N個(gè)數(shù):A[1],A[2],…,A[N],求出最長(zhǎng)非降子序列的長(zhǎng)度。 (講DP基本都會(huì)講到的一個(gè)問(wèn)題LIS:longest increasing subsequence)
正如上面我們講的,面對(duì)這樣一個(gè)問(wèn)題,我們首先要定義一個(gè)“狀態(tài)”來(lái)代表它的子問(wèn)題,并且找到它的解。注意,大部分情況下,某個(gè)狀態(tài)只與它前面出現(xiàn)的狀態(tài)有關(guān),而獨(dú)立于后面的狀態(tài)。
讓我們沿用“入門(mén)”一節(jié)里那道簡(jiǎn)單題的思路來(lái)一步步找到“狀態(tài)”和“狀態(tài)轉(zhuǎn)移方程”。 假如我們考慮求A[1],A[2],…,A[i]的最長(zhǎng)非降子序列的長(zhǎng)度,其中i<N,那么上面的問(wèn)題變成了原問(wèn)題的一個(gè)子問(wèn)題(問(wèn)題規(guī)模變小了,你可以讓i=1,2,3等來(lái)分析) 然后我們定義d(i),表示前i個(gè)數(shù)中以A[i]結(jié)尾的最長(zhǎng)非降子序列的長(zhǎng)度。OK,對(duì)照“入門(mén)”中的簡(jiǎn)單題,你應(yīng)該可以估計(jì)到這個(gè)d(i)就是我們要找的狀態(tài)。如果我們把d(1)到d(N)都計(jì)算出來(lái),那么最終我們要找的答案就是這里面最大的那個(gè)。狀態(tài)找到了,下一步找出狀態(tài)轉(zhuǎn)移方程。
為了方便理解我們是如何找到狀態(tài)轉(zhuǎn)移方程的,我先把下面的例子提到前面來(lái)講。如果我們要求的這N個(gè)數(shù)的序列是:
5,3,4,8,6,7
根據(jù)上面找到的狀態(tài),我們可以得到:(下文的最長(zhǎng)非降子序列都用LIS表示)
· 前1個(gè)數(shù)的LIS長(zhǎng)度d(1)=1(序列:5)
· 前2個(gè)數(shù)的LIS長(zhǎng)度d(2)=1(序列:3;3前面沒(méi)有比3小的)
· 前3個(gè)數(shù)的LIS長(zhǎng)度d(3)=2(序列:3,4;4前面有個(gè)比它小的3,所以d(3)=d(2)+1)
· 前4個(gè)數(shù)的LIS長(zhǎng)度d(4)=3(序列:3,4,8;8前面比它小的有3個(gè)數(shù),所以 d(4)=max{d(1),d(2),d(3)}+1=3)
OK,分析到這,我覺(jué)得狀態(tài)轉(zhuǎn)移方程已經(jīng)很明顯了,如果我們已經(jīng)求出了d(1)到d(i-1),那么d(i)可以用下面的狀態(tài)轉(zhuǎn)移方程得到:
d(i) = max{1, d(j)+1},其中j<i,A[j]<=A[i]
用大白話解釋就是,想要求d(i),就把i前面的各個(gè)子序列中,最后一個(gè)數(shù)不大于A[i]的序列長(zhǎng)度加1,然后取出最大的長(zhǎng)度即為d(i)。當(dāng)然了,有可能i前面的各個(gè)子序列中最后一個(gè)數(shù)都大于A[i],那么d(i)=1,即它自身成為一個(gè)長(zhǎng)度為1的子序列。
分析完了,上圖:(第二列表示前i個(gè)數(shù)中LIS的長(zhǎng)度,第三列表示,LIS中到達(dá)當(dāng)前這個(gè)數(shù)的上一個(gè)數(shù)的下標(biāo),根據(jù)這個(gè)可以求出LIS序列)

#include <cstdio> #include <iostream> #include <algorithm> #include <cstring> usingnamespace std; int main() { int dp[2000],a[2000],n; while(cin>>n) { memset(dp,0,sizeof(dp)); intres = 0; for(inti = 0; i < n; i++) cin>>a[i]; . for(inti = 0; i < n; i++) { dp[i] = 1; for(intj = 0; j < i; j++) { if(a[j] < a[i]) dp[i] = max(dp[i],dp[j] + 1); } res = max(res,dp[i]); } cout<<res<<endl; } return0; }

總結(jié):

1,縮小規(guī)模,子問(wèn)題本質(zhì)上還是原問(wèn)題

2,找狀態(tài)和狀態(tài)轉(zhuǎn)移方程

總結(jié)

以上是生活随笔為你收集整理的动态规划思想的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。