如圖所示的數字三角形,從頂部出發,在每一結點可以選擇向左走或得向右走,一直走到底層,要求找出一條路徑,使路徑上的值最大。
第一行是數塔層數N(1<=N<=100)。
第二行起,按數塔圖形,有一個或多個的整數,表示該層節點的值,共有N行。
輸出最大值。
5
13
11 8
12 7 26
6 14 15 8
12 7 13 24 11
86
解題思路:
參考北大郭煒老師
用二維數組存放數字三角形。
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行的三角形:
if ( r == N) MaxSum(r,j) = D(r,j)
else MaxSum( r, j) = Max{ MaxSum(r+1,j), MaxSum(r+1,j+1) } + D(r,j)
下面四段代碼分別是存粹遞歸、記憶型遞歸、遞推、空間優化的遞推的代碼:
1 #include <stdio.h> 2 3 #define maxN 101 4 5 int N; 6 int D[maxN][maxN]; //D[i][j]表示第i行第 j 個數字。其中i、j從1開始算 7 8 int maxSum[maxN][maxN]; //maxSum[i][j]表示從D(i,j)到底邊的各條路徑中,最佳路徑的數字之和。 9 10 11 //代碼一:純粹遞歸。當N達到100是絕對是超時的。因為復雜度是O(2^N) 12 int fun1(int i,int j)//返回從(i,j)到達最底層的最大路徑之和 13 { 14 if(i==N) return D[i][j]; 15 else 16 { 17 int x=fun1(i+1,j); 18 int y=fun1(i+1,j+1); 19 return D[i][j]+(x>y?x:y); 20 } 21 } 22 23 //代碼二:記憶型遞歸,避免重復計算。時間復雜度O(n*n) 24 int fun2(int i,int j)//返回從(i,j)到達最底層的最大路徑之和 25 { 26 if(maxSum[i][j]!=-1) return maxSum[i][j]; 27 28 if(i==N) maxSum[i][j]=D[i][j]; 29 else 30 { 31 int x=fun2(i+1,j); 32 int y=fun2(i+1,j+1); 33 maxSum[i][j]=D[i][j]+(x>y?x:y); 34 } 35 36 return maxSum[i][j]; 37 } 38 39 //代碼三:遞歸變遞推 40 int fun3() 41 { 42 int i,j; 43 for(j=1;j<=N;j++) maxSum[N][j]=D[N][j]; 44 45 for(i=N-1;i>=1;i--) 46 { 47 for(j=1;j<=i;j++) 48 { 49 int max=(maxSum[i+1][j]>maxSum[i+1][j+1]?maxSum[i+1][j]:maxSum[i+1][j+1]); 50 maxSum[i][j]=D[i][j]+max; 51 } 52 } 53 return maxSum[1][1]; 54 } 55 56 //代碼四:遞歸變遞推並在空間上做優化 57 int fun4() 58 { 59 int i,j; 60 61 for(i=N-1;i>=1;i--) 62 { 63 for(j=1;j<=i;j++) 64 { 65 int max=(D[i+1][j]>D[i+1][j+1]?D[i+1][j]:D[i+1][j+1]); 66 D[i][j]=D[i][j]+max; 67 } 68 } 69 return D[1][1]; 70 } 71 72 int main(int argc, char *argv[]) 73 { 74 int i,j; 75 freopen("001.in","r",stdin); 76 scanf("%d",&N); 77 for(i=1;i<=N;i++) 78 { 79 for(j=1;j<=i;j++) 80 { 81 scanf("%d",&D[i][j]); 82 maxSum[i][j]=-1; 83 } 84 } 85 86 //printf("%d\n",fun1(1,1)); 87 //printf("%d\n",fun2(1,1)); 88 //printf("%d\n",fun3()); 89 printf("%d\n",fun4()); 90 return 0; 91 }
補充一個深度優先搜索的實現。僅僅只是演示一下算法,時間復雜度應該是很高,無法AC的。

1 #include<stdio.h> 2 #define maxN 101 3 int n,a[maxN][maxN]={0}; 4 int xx[2]={1,1}; 5 int yy[2]={0,1}; 6 int ans=0,maxAns=0; 7 void dfs(int x,int y) 8 { 9 int i; 10 for(i=0;i<2;i++) 11 { 12 int newX=x+xx[i]; 13 int newY=y+yy[i]; 14 if(newX<=n&&newY<=n) 15 { 16 ans=ans+a[newX][newY]; 17 if(newX==n) 18 { 19 if(ans>maxAns) maxAns=ans; 20 } 21 else dfs(newX,newY); 22 ans=ans-a[newX][newY]; 23 } 24 } 25 } 26 int main() 27 { 28 int i,j; 29 freopen("data.in","r",stdin); 30 scanf("%d",&n); 31 for(i=1;i<=n;i++) 32 for(j=1;j<=i;j++) scanf("%d",&a[i][j]); 33 34 ans=maxAns=a[1][1]; 35 dfs(1,1); 36 37 printf("%d\n",maxAns); 38 return 0; 39 }
其實本質和上面遞歸實現的做法是一致的,只是代碼實現框架不同而已。
2193 數字三角形WW
題目鏈接:http://codevs.cn/problem/2193/
本道題目由上面數字三角形變化而來。具體變化是:從數字三角形頂部往底層走時必須經過某一個指定點,使之走的路程和最大
第1行n,表示n行
第2到n+1行為每個的權值
程序必須經過n div 2,n div 2這個點
最大值
2
1
1 1
2
n <=25
算法分析
這道題只需讓必須經過的點的權值加上一個特別大的值,最后的結果再減去這個值就行了。實際上,狀態轉移方程和上面第一題是沒有變的。
我這里在輸入時順便把所有元素累加求和得到sum。然后把這個sum加到必須走的那個點。
1 #include <stdio.h> 2 #define maxN 101 3 int n,a[maxN][maxN]; 4 int main(int argc, char *argv[]) 5 { 6 int i,j,x,y,sum=0; 7 scanf("%d",&n); 8 for(i=1;i<=n;i++) 9 for(j=1;j<=i;j++) 10 { scanf("%d",&a[i][j]); sum=sum+a[i][j]; } 11 x=n/2; y=n/2; 12 a[x][y]+=sum; 13 for(i=n-1;i>=1;i--) 14 { 15 for(j=1;j<=i;j++) 16 { 17 a[i][j]+=(a[i+1][j]>a[i+1][j+1]?a[i+1][j]:a[i+1][j+1]); 18 } 19 } 20 printf("%d\n",a[1][1]-sum); 21 return 0; 22 }
2198 數字三角形WWW
題目鏈接:http://codevs.cn/problem/2198/
本道題目由上面第一題數字三角形變化而來。具體變化是:從數字三角形必須經過某一個點,使之走的路程和最大
第1行n,表示n行
第2到n+1行為每個的權值
第n+2行為兩個數x,y表示必須經過的點
最大值
2
1
1 1
1 1
2
n<=25
算法分析
這個題目和第二題並沒什么區別,唯一區別就是輸入時多輸入x和y表示必須要走過的點。
1 #include <stdio.h> 2 #define maxN 101 3 int n,a[maxN][maxN]; 4 int main(int argc, char *argv[]) 5 { 6 int i,j,x,y,sum=0; 7 scanf("%d",&n); 8 for(i=1;i<=n;i++) 9 for(j=1;j<=i;j++) 10 { scanf("%d",&a[i][j]); sum=sum+a[i][j]; } 11 //x=n/2; y=n/2; 12 scanf("%d%d",&x,&y); 13 a[x][y]+=sum; 14 for(i=n-1;i>=1;i--) 15 { 16 for(j=1;j<=i;j++) 17 { 18 a[i][j]+=(a[i+1][j]>a[i+1][j+1]?a[i+1][j]:a[i+1][j+1]); 19 } 20 } 21 printf("%d\n",a[1][1]-sum); 22 return 0; 23 }
上面三道題目的代碼中用到的動規狀態轉移方程都是逆推,從最后一行往前遞推。其實也是可以順着推的。比如第三題的代碼可以像下面這一段這么寫:(下面代碼中,f[i][j]表示的是從(i,j)這個點到達(1,1)這個點的最大路徑之和。另外,這里用了兩個數組,其實僅用一個數組即可,類似上述代碼。)
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 using namespace std; 5 int n,x,y,w[35][35],ko; 6 long long f[35][35]; 7 int main() 8 { 9 scanf("%d",&n); 10 for(int i=1;i<=n;i++) 11 for(int j=1;j<=i;j++) 12 scanf("%d",&w[i][j]); 13 scanf("%d%d",&x,&y); 14 w[x][y]+=99999999; 15 f[1][1]=w[1][1]; 16 for(int i=2;i<=n;i++) 17 for(int j=1;j<=i;j++) 18 f[i][j]=w[i][j]+max(f[i-1][j],f[i-1][j-1]); 19 long long ans=0; 20 for(int i=1;i<=n;i++) 21 ans=max(ans,f[n][i]); 22 printf("%lld",ans-99999999); 23 return 0; 24 }
2189 數字三角形W
題目鏈接:http://codevs.cn/problem/2189/
這個題目仍然是由上面第一題的數字三角形變化而來。具體如下:
數字三角形
要求走到最后mod 100最大
第1行n,表示n行
第2到n+1行為每個的權值
mod 100最大值
2
1
99 98
99
n<=25
算法分析
這個題目的改動使得問題處理起來比較麻煩了。本來比較大的一個數,加上那么一點點,再取模,可能就很小了。顯然這已經不再滿足動態規划的無后效性原則了。怎么辦呢?
可以開一個布爾型的三維數組,用f[i][j][k]表示走到位置(i,j)時路徑權值之和再取模能否得到k這個值,於是得到這樣一個狀態轉移方程:
f[i][j][k]=f[i][j][k] or f[i-1][j][(k-a[i][j]+m)%m] or f[i-1][j-1][(k-a[i][j]+m)%m],這里面加上m是為了防止出現負下標。
最后找一遍那個目標狀態存在就行了。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 const int MAXN=26; 6 int n; 7 int a[MAXN][MAXN]; 8 bool f[MAXN][MAXN][101]; 9 int main() 10 { 11 scanf("%d",&n); 12 for(int i=1;i<=n;i++) 13 for(int j=1;j<=i;j++) 14 cin>>a[i][j]; 15 16 for(int i=1;i<=n;i++)f[n][i][a[n][i]%100]=1; 17 18 for(int i=n-1;i>=1;i--) 19 for(int j=1;j<=i;j++) 20 for(int k=0;k<=99;k++) 21 f[i][j][k]=f[i+1][j][(k-a[i][j]+100)%100]||f[i+1][j+1][(k-a[i][j]+100)%100]; 22 23 for(int k=100;k>=1;k--) 24 if(f[1][1][k]==1){cout<<k;break;} 25 return 0; 26 }
下面這一段代碼可能比較好理解:
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 using namespace std; 5 int n,a[26][26]={0}; 6 bool f[26][26][102]={0}; 7 int main() 8 { 9 cin>>n; 10 for (int i=1;i<=n;i++) //數據的讀入 11 for (int j=1;j<=i;j++) 12 cin>>a[i][j]; 13 14 for (int i=1;i<=n;i++) //這一步的代碼是為了將最后一排的所有數存下來 15 f[n][i][a[n][i]]=true; 16 17 for (int i=n-1;i>=1;--i) //這里是主要過程,思路:將所有的可能性mod100都存下來, 18 for (int j=1;j<=i;j++) 19 for (int k=0;k<=99;k++) 20 { 21 if (f[i+1][j][k]) f[i][j][(k+a[i][j])%100]=true; 22 if (f[i+1][j+1][k]) f[i][j][(k+a[i][j])%100]=true; 23 } 24 for (int k=99;k>=0;--k) //找出所有可能性中最大的可能,輸出,這個題就AC咯、 25 if (f[1][1][k]) 26 { 27 cout<<k; 28 return 0; 29 } 30 }
4829 [DP]數字三角形升級版 & 4832 [DP]數字三角形升級版
(ps:兩道題其實是一樣的,據說數據規模不同)
題目仍然是由上面第一題的數字三角形變化而來,具體如下:
從數字三角形的頂部到底部有很多條不同的路徑。對於每條路徑,把路徑上面的數加起來可以得到一個和,且你有一次機會可以把任何一個數重復加一遍。
和最大的路徑稱為最佳路徑。你的任務就是求出最佳路徑上的數字之和。
第一行:一個數,表示行數。
接下來n行為數塔
一個數即最優結果
5
1
1 3
1 1 3
1 1 1 3
7 1 1 1 3
18
三角形行數不大於1000。最大和不大於maxlongint
算法分析
這個似乎有點類似於背包問題的回溯法求解。可以對動規數組增加一維,用於記錄從底層到達(i,j)這個位置的路徑上是否曾經有數字被重復使用過。具體參見以下代碼:
(代碼來自codevs討論版塊)
1 #include<iostream> 2 using namespace std; 3 int n; 4 int a[1000][1000]; 5 int f[1000][1000][2]; 6 int i,j; 7 int main() 8 { 9 cin>>n; 10 for(i=0;i<n;i++) 11 for(j=0;j<=i;j++)cin>>a[i][j]; 12 for(i=0;i<n;i++){ 13 f[n-1][i][0]=a[n-1][i]; 14 f[n-1][i][1]=a[n-1][i]<<1;//f[n-1][i][1]=a[n-1][i]*2; 15 } 16 for(i=n-2;i>=0;i--) 17 for(j=0;j<=i;j++){ 18 f[i][j][0]=a[i][j]+max(f[i+1][j][0],f[i+1][j+1][0]); 19 f[i][j][1]=a[i][j]+max(f[i+1][j][1],f[i+1][j+1][1]); 20 f[i][j][1]=max(f[i][j][1],f[i][j][0]+a[i][j]); 21 } 22 cout<<f[0][0][1]<<endl; 23 return 0; 24 }
再補充一道數字三角形變形的題目:
關於數字三角形,還有一題:5585 數字三角形第k優解,暫時未搞懂怎解,希望各位大神留言解答呵呵。
參考資料:
【強烈建議閱讀】http://blog.csdn.net/Little_Flower_0/article/details/47945611
【強烈建議閱讀】http://blog.csdn.net/qq_35776409/article/details/62890528
http://www.cnblogs.com/zwfymqz/p/6790960.html
http://www.genshuixue.com/i-cxy/p/8020923