-
总时间限制:
- 1000ms 内存限制:
- 65536kB
-
描述
-
7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 (图1)
图1给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和。
注意:路径上的每一步只能从一个数走到下一层上和它最近的左边的那个数或者右边的那个数。
输入
- 输入的是一行是一个整数N (1 < N <= 100),给出三角形的行数。下面的N行给出数字三角形。数字三角形上的数的范围都在0和100之间。 输出
- 输出最大的和。 样例输入
-
5 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5
样例输出
-
30
解题思路:
这道题目可以用递归的方法解决。基本思路是:以D( r, j)表示第r 行第 j 个数字(r,j 都从1 开始算),以MaxSum(r, j) 代表从第 r 行的第 j 个数字到底边的最佳路径的数字之和,则本题是要求 MaxSum(1, 1) 。从某个D(r, j)出发,显然下一步只能走D(r+1, j)或者D(r+1, j+1)。如果走D(r+1, j),那么得到的MaxSum(r, j)就是MaxSum(r+1, j) + D(r, j);如果D(r+1, j+1),那么得到的MaxSum(r, j)就是MaxSum(r+1, j+1) + D(r, j)。所以,选择往哪里走,就看MaxSum(r+1, j)和MaxSum(r+1, j+1)哪个更大了。程序如下:
-
//从上往下
//递归思想
-
#include<stdio.h> #define Max 100 int N; int D[Max+10][Max+10]; int MaxSum(int r,int j) { if(r==N) return D[r][j]; int nSum1=MaxSum(r+1,j); int nSum2=MaxSum(r+1,j+1); if(nSum1>nSum2) return nSum1+D[r][j]; return nSum2+D[r][j]; } int main() { int m; scanf("%d",&N); for(int i=1;i<=N;i++) for(int j=1;j<=i;j++) scanf("%d",&D[i][j]); printf("%d\n",MaxSum(1,1)); return 0; }
上面的程序,效率非常低,在N值并不大,比如N=100的时候,就慢得几乎永远算不出结果了, 为什么会这样呢?是因为过多的重复计算。我们不妨将对MaxSum函数的一次调用称为一次计算。那么,每次计算MaxSum(r,j)的时候,都需要计算一次MaxSum(r+1,j),而每次计算MaxSum(r,j+1)的时候,也要计算一次MaxSum(r+1,j),重复计算因此产生,在题目中给出的例子里,如果我们将MaxSum(r,j)被计算的次数都写在位置(r,j),那么就能得到下面的三角形:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1 -
从上图可以看出,最后一行的计算次数总和是16,倒数第二行的计算次数总和是8。不难总结出规律,对于N 行的三角形,总的计算次数是20 +21+22……2N-1=2N。当N= 100 时,总的计算次数是一个让人无法接受的大数字。既然问题出在重复计算,那么解决的办法,当然就是,一个值一旦算出来,就要记住,以后不必重新计算。即第一次算出MaxSum(r, j)的值时,就将该值存放起来,下次再需要计算MaxSum(r, j)时,直接取用存好的值即可,不必再次调用MaxSum 进行函数递归计算了。这样,每个MaxSum(r, j)都只需要计算1 次即可,那么总的计算次数(即调用MaxSum 函数的次数)就是三角形中的数字总数,即1+2+3+……N = N(N+1)/2如何存放计算出来的MaxSum(r, j)值呢?显然,用一个二维数组aMaxSum[N][N]就能解决。aMaxSum[r][j]就存放MaxSum(r, j)的计算结果。下次再需要MaxSum(r, j)的值时,不必再调用MaxSum 函数,只需直接取aMaxSum[r][j]的值即可。程序下:
-
//从上往下
-
//动态规划
-
#include<stdio.h> #include<string.h> #define Max 100 int N; int D[Max+10][Max+10]; int aMaxSum[Max+10][Max+10];//用一个二维数组aMaxSum[i][j]存放MaxSum(i,j)里面的值 int MaxSum(int r,int j) { if(r==N) return D[r][j]; if(aMaxSum[r+1][j]==-1) aMaxSum[r+1][j]=MaxSum(r+1,j); if(aMaxSum[r+1][j+1]==-1) aMaxSum[r+1][j+1]=MaxSum(r+1,j+1); if(aMaxSum[r+1][j]>aMaxSum[r+1][j+1]) return aMaxSum[r+1][j]+D[r][j]; return aMaxSum[r+1][j+1]+D[r][j]; } int main() { int m; scanf("%d",&N); //将aMaxSum全部置成-1,表示开始所有的MaxSum(r,j)都没有算过 memset(aMaxSum,-1,sizeof(aMaxSum)); for(int i=1;i<=N;i++) for(int j=1;j<=i;j++) scanf("%d",&D[i][j]); printf("%d\n",MaxSum(1,1)); return 0; }
这种将一个问题分解为子问题递归求解,并且将中间结果保存以避免重复计算的办法,就叫做“动态规划”。动态规划通常用来求最优解,能用动态规划解决的求最优解问题,必须满足,最优解的每个局部解也都是最优的。以上题为例,最佳路径上面的每个数字到底部的那一段路径,都是从该数字出发到达到底部的最佳路径。因此,不需要写递归函数,从aMaxSum[N-1]这一行元素开始向上逐行递推,就能求得最终aMaxSum[1][1]的值了。程序如下:
-
//从下往上(每次选择最大的值往上累加)
-
//动态规划
-
#include<stdio.h> #include<string.h> #define Max 100 int N; int D[Max+10][Max+10]; int aMaxsum[Max+10][Max+10]; int main() { int i,j; scanf("%d",&N); for(i=1;i<=N;i++) for(j=1;j<=i;j++) scanf("%d",&D[i][j]); for(j=1;j<=N;j++) aMaxsum[N][j]=D[N][j]; for(i=N;i>1;i--) for(j=1;j<i;j++){ if(aMaxsum[i][j]>aMaxsum[i][j+1]) aMaxsum[i-1][j]=aMaxsum[i][j]+D[i-1][j]; else aMaxsum[i-1][j]=aMaxsum[i][j+1]+D[i-1][j]; } printf("%d\n",aMaxsum[1][1]); return 0; }
-
-
-
摘要 《程序设计导引及在线实践》