數字三角形(poj1163)
在上面的數字三角形中尋找一條從頂部到底邊的路徑,使得路徑上所經過的數字之和最大。路徑上的每一步都只能往左下或 右下走。只需要求出這個最大和即可,不必給出具體路徑。 三角形的行數大於1小於等於100,數字為 0 - 99
輸入格式:
5 //表示三角形的行數 接下來輸入三角形
3 88 1 0
2 7 4 4
4 5 2 6 5
要求輸出最大和
接下來,我們來分析一下解題思路:
首先,肯定得用二維陣列來存放數字三角形
然後我們用d( r, j) 來表示第r行第 j 個數字(r,j從1開始算)
我們用maxsum(r, j)表示從d(r,j)到底邊的各條路徑中,最佳路徑的數字之和。
因此,此題的最終問題就變成了求 maxsum(1,1)
當我們看到這個題目的時候,首先想到的就是可以用簡單的遞迴來解題:
d(r, j)出發,下一步只能走d(r+1,j)或者d(r+1, j+1)。故對於n行的三角形,我們可以寫出如下的遞迴式:
[cpp]view plaincopy
if(r==n) maxsum(r,j)=d(r,j) else maxsum(r,j)=max+d(r,j)
根據上面這個簡單的遞迴式,我們就可以很輕鬆地寫出完整的遞迴**:
[cpp]view plaincopy
#include #include #definemax101 usingnamespacestd; intd[max][max]; intn; intmaxsum(inti,intj) intmain() returnmaxsum[i][j]; } intmain() cout當我們提交如上**時,結果就是一次ac
雖然在短時間內就ac了。但是,我們並不能滿足於這樣的**,因為遞迴總是需要使用大量堆疊上的空間,很容易造成棧溢位,我們現在就要考慮如何把遞迴轉換為遞推,讓我們一步一步來完成這個過程。
我們首先需要計算的是最後一行,因此可以把最後一行直接寫出,如下圖:
現在開始分析倒數第二行的每乙個數,現分析數字2,2可以和最後一行4相加,也可以和最後一行的5相加,但是很顯然和5相加要更大一點,結果為7,我們此時就可以將7儲存起來,然後分析數字7,7可以和最後一行的5相加,也可以和最後一行的2相加,很顯然和5相加更大,結果為12,因此我們將12儲存起來。以此類推。。我們可以得到下面這張圖:
然後按同樣的道理分析倒數第三行和倒數第四行,最後分析第一行,我們可以依次得到如下結果:
上面的推導過程相信大家不難理解,理解之後我們就可以寫出如下的遞推型動態規劃程式:
[cpp]view plaincopy
#include #include usingnamespacestd; #definemax101 intd[max][max]; intn; intmaxsum[max][max]; intmain(){ inti,j; cin>>n; for(i=1;i<=n;i++) for(j=1;j<=i;j++) cin>>d[i][j]; for(inti=1;i<=n;++i) maxsum[n][i]=d[n][i]; for(inti=n-1;i>=1;--i) for(intj=1;j<=i;++j) maxsum[i][j]=max(maxsum[i+1][j],maxsum[i+1][j+1])+d[i][j]; cout我們的**僅僅是這樣就夠了嗎?當然不是,我們仍然可以繼續優化,而這個優化當然是對於空間進行優化,其實完全沒必要用二維maxsum陣列儲存每乙個maxsum(r,j),只要從底層一行行向上遞推,那麼只要一維陣列maxsum[100]即可,即只要儲存一行的maxsum值就可以。
對於空間優化後的具體遞推過程如下:
接下裡的步驟就按上圖的過程一步一步推導就可以了。進一步考慮,我們甚至可以連maxsum陣列都可以不要,直接用d的第n行直接替代maxsum即可。但是這裡需要強調的是:雖然節省空間,但是時間複雜度還是不變的。
依照上面的方式,我們可以寫出如下**:
[cpp]view plaincopy
#include #include usingnamespacestd; #definemax101 intd[max][max]; intn; int*maxsum; intmain(){ inti,j; cin>>n; for(i=1;i<=n;i++) for(j=1;j<=i;j++) cin>>d[i][j]; maxsum=d[n];//maxsum指向第n行 for(inti=n-1;i>=1;--i) for(intj=1;j<=i;++j) maxsum[j]=max(maxsum[j],maxsum[j+1])+d[i][j]; cout<
接下來,我們就進行一下總結:
遞迴到動規的一般轉化方法
遞迴函式有n個引數,就定義乙個n維的陣列,陣列的下標是遞迴函式引數的取值範圍,陣列元素的值是遞迴函式的返回值,這樣就可以從邊界值開始, 逐步填充陣列,相當於計算遞迴函式值的逆過程。
動規解題的一般思路
1. 將原問題分解為子問題
把原問題分解為若干個子問題,子問題和原問題形式相同或類似,只不過規模變小了。子問題都解決,原問題即解決(數字三角形例)。 子問題的解一旦求出就會被儲存,所以每個子問題只需求 解一次。
2.確定狀態
在用動態規劃解題時,我們往往將和子問題相關的各個變數的一組取值,稱之為乙個「狀 態」。乙個「狀態」對應於乙個或多個子問題, 所謂某個「狀態」下的「值」,就是這個「狀 態」所對應的子問題的解。 所有「狀態」的集合,構成問題的「狀態空間」。「狀態空間」的大小,與用動態規劃解決問題的時間複雜度直接相關。 在數字三角形的例子裡,一共有n(n+1)/2個數字,所以這個問題的狀態空間裡一共就有n(n+1)/2個狀態。
整個問題的時間複雜度是狀態數目乘以計算每個狀態所需時間。在數字三角形裡每個「狀態」只需要經過一次,且在每個狀態上作計算所花的時間都是和n無關的常數。
3.確定一些初始狀態(邊界狀態)的值
以「數字三角形」為例,初始狀態就是底邊數字,值就是底邊數字值。
4. 確定狀態轉移方程
定義出什麼是「狀態」,以及在該「狀態」下的「值」後,就要找出不同的狀態之間如何遷移――即如何從乙個或多個「值」已知的 「狀態」,求出另乙個「狀態」的「值」(遞推型)。狀態的遷移可以用遞推公式表示,此遞推公式也可被稱作「狀態轉移方程」。
數字三角形的狀態轉移方程:
能用動規解決的問題的特點
1) 問題具有最優子結構性質。如果問題的最優解所包含的 子問題的解也是最優的,我們就稱該問題具有最優子結 構性質。
2) 無後效性。當前的若干個狀態值一旦確定,則此後過程的演變就只和這若干個狀態的值有關,和之前是採取哪種手段或經過哪條路徑演變到當前的這若干個狀態,沒有關係。
動態規劃演算法
一 動態規劃演算法原理 將待求解的問題分解成若干個相互聯絡的子問題,先求解子問題,然後從這些子問題的解得到原問題的解 對於重複出現的子問題,只在第一次遇到的時候對它進行求解,並把答案儲存起來。了不去求解相同的子問題,引入乙個陣列,把所有子問題的解存於該陣列中,這就是動態規劃所採用的基本方法。動態規劃...
動態規劃演算法
動態規劃 通過把原問題分解為相對簡單的子問題來求解複雜問題。動態規劃常常適用於有重疊子問題和最優子結構性質的問題。演算法總體思想 演算法的基本步驟 演算法的基本要素 最優子結構 重疊子問題 備忘錄方法 問題描述 子串行 公共子串行 最長公共子串行 lcs 問題 問題分析 動態規劃求解lcs問題 最長...
動態規劃演算法
動態規劃演算法的思路 動態規劃法即 dynamic programming method dp 是系統分析中的種常用方法。動態規劃法是20世紀50年代由貝爾曼 r.bellman 等人提出的,用來解決多階段決策過程問題的一種最優化方法。多階段決策過程是指把研究問題分成若干個相互聯絡的階段,由每個階段...