數塔問題

【問題描述】

考慮在下面被顯示的數字金字塔。

寫一個程序來計算從最高點開始在底部任意處結束的路徑經過數字的和的最大(小)。每一步可以走到左下方的點也可以到達右下方的點。

7

3   8

8   1   0

2   7   4   4

4   5   2   6   5

在上面的樣例中, 3  8  7  5 的路徑產生了最大和:30

【輸入文件】

第一個行包含 R(1<= R<=1000) ,表示行的數目。

後面每行爲這個數字金字塔特定行包含的整數。

所有被供應的整數是非負的且不大於100

【輸出文件】

單獨的一行包含那個可能得到的最大的和。

【輸入樣例】

5

7

3 8

8 1 0

2 7 4 4

4 5 2 6 5

【輸出樣例】

30

【提交鏈接】

http://poj.org/problem?id=1163

【問題分析】

這個問題是學習動態規劃最簡單最經典的問題,說它經典是因爲它的階段、狀態、決策都十分明顯。

剛看到題目覺得沒有入手點,連怎麼儲存,描述這個金字塔都是問題,看輸入樣例發現:數字金字塔可以變成像輸入樣例那樣的下三角,這樣可以用一個二維數組a儲存它,並且可以用(i,j)描述一個數字在金字塔中的位置。

對於中間的一個點來說,想經過它則必須經過它的上方或左上(針對變化後的三角形)。也就是說經過這個點的數字和最大等於經過上方或左上方所得的“最大和”中一個更大的加上這個點中的數字。顯然這個定義滿足最優子結構。

這樣階段很明顯就是金字塔的層,設計一個二維狀態opt[i,j]表示走到第i行第j列時經過的數字的最大和。決策就是opt[i-1,j] opt[i-1,j-1]中一個更大的加上(i,j)點的數字。

對於一個點只考慮上面或左上即前一階段,滿足無後效性。

狀態轉移方程(順推):

opt[i-1,j]+a[i,j]                         (j=1)

opt[i,j]=   opt[i-1,j-1]+ a[i,j]                      (j=i)

max{opt[i-1,j],opt[i-1,j-1]}+ a[i,j]  (1<j<i)

實現時可以將opt[i,j]的左右邊界定義的大點,初始opt[i,j]=0

由於在j=1opt[i-1,j-1]=0,opt[i-1,j]>=0所以方程也可以這樣寫:

opt[i,j]=max{opt[i-1,j],opt[i-1,j-1]}+a[i,j]

同理j=i時方程也可以寫成上面那樣,所以方程綜合爲:

opt[i,j]=max{opt[i-1,j],opt[i-1,j-1]}+a[i,j]0<j<=i

顯然答案是走到底後的一個最大值,即:

ans=max{opt[n,i]}           (1<=i<=n)

其實從上往下走和從下往上走結果是一樣的,但是如果從下往上走結果就是opt[1,1]省下求最大值了,所以方程進一步改動(逆推):

opt[i,j]=max{opt[i+1,j],opt[i+1,j+1]}+a[i,j]0<j<=i

複雜度:狀態數ON2*轉移代價O1=ON2)。

C/C++參考代碼:

//http://www.structea.cn/post/22.html
//*DP--順推法
//求最小路徑得分
//遞歸方程
//D(X,1)=D(X-1,1)+a(X,1)
//D(X,y)=min{D(X-1,y),D(X-1,y-1)}+a(X,y)
//D(1,1)=a(1,1),k=1,…,N
//D(N,i),i=1,2,…,N,中最小的爲最小路徑得分
//PKU1163、HDU2084
 
#include <iostream>
using namespace std;
int sou[100][100];
int main()
{
     int i,j,n,tmp;
     cin>>n;                      //輸入數據行數
     for (i=0;i<n;i++)
         for (j=0;j<=i;j++)
              cin>>sou[i][j];      //輸入各行數據
     for (i=1;i<n;i++)
     {
         sou[i][0]+=sou[i-1][0];
         for (j=1;j<i;j++)
         {
              tmp=sou[i-1][j-1];
              if (sou[i-1][j]<tmp)
                   tmp=sou[i-1][j];
              sou[i][j]+=tmp;
         }
         sou[i][j]+=sou[i-1][j-1];
     }
     tmp=sou[n-1][0];
     for (i=0;i<n;i++)
         if (sou[n-1][i]<tmp)
              tmp=sou[n-1][i];
     cout<<tmp<<endl;
     return 0;
}
//DP--逆推法
//求最小路徑得分
//遞歸方程
//D(X,y)=min{D(X+1,y),D(X+1,y+1)}+a(X,y)
//D(N,k)=a(N,k),k=1,…,N
//D(1,1)即爲最小路徑得分
/*
#include <iostream>
using namespace std;
int sou[100][100];
int main()
{
     int i,j,n,tmp;
     cin>>n;                      //輸入數據行數
     for (i=0;i<n;i++)
         for (j=0;j<=i;j++)
              cin>>sou[i][j];      //輸入各行數據
     for (i=n-2;i>=0;i--)
     {
         for (j=0;j<=i;j++)
         {
              tmp=sou[i+1][j];
              if (sou[i+1][j+1]>tmp)
                   tmp=sou[i+1][j+1];
              sou[i][j]+=tmp;
         }
     }
     cout<<sou[0][0]<<endl;
     return 0;
}*/
/*塗亞運解
#include<iostream>
using namespace std;
#define MAXN 1001
int g[MAXN][MAXN];
int max(int a,int b)
{
     return a>b?a:b;
}
int main()
{
     int n,i,j;
     while(cin>>n)
     {
         for(i=0;i<n;i++)
              for(j=i;j>=0;j--)
                   scanf("%d",&g[i][j]);
        
         for(i=n-2;i>=0;i--)
              for(j=0;j<n-1;j++)
                   g[i][j]+=max(g[i+1][j],g[i+1][j+1]);     
        
         cout<<g[0][0]<<endl;
     }
     return 0;
}
*/



除非註明,本站文章均爲原創,轉載請註明: 文章來自世界大學城

 轉載自:http://www.worlduc.com/blog2012.aspx?bid=707211

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章