树塔

数塔

Time Limit : 1000/1000ms (Java/Other)   Memory Limit : 32768/32768K (Java/Other)
Total Submission(s) : 9   Accepted Submission(s) : 9
Font: Times New Roman | Verdana | Georgia
Font Size: ← →

Problem Description

在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的:

有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?

已经告诉你了,这是个DP的题目,你能AC吗?

Input

输入数据首先包括一个整数C,表示测试实例的个数,每个测试实例的第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。

Output

对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。

Sample Input

1
5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5

Sample Output

30
 
 
  
  
  
  
  1. 数塔问题:
  2. 9
  3. 12 15
  4. 10 6 8
  5. 2 18 9 5
  6. 19 7 10 4 16
  7. 有形如图所示的数塔,从顶部出发,在每一结点可以选择向左走或是向右走,
  8. 一直走到底层,要求找出一条路径,使路径上的值最大。
  9. 这道题如果用枚举法,在数塔层数稍大的情况下(如40),则需要列举出的路径条数将是一个非常庞大的数目。
  10. 如果用贪心法又往往得不到最优解。
  11. 在用动态规划考虑数塔问题时可以自顶向下的分析,自底向上的计算。
  12. 从顶点出发时到底向左走还是向右走应取决于是从左走能取到最大值还是从右走能取到最大值,
  13. 只要左右两道路径上的最大值求出来了才能作出决策。
  14. 同样的道理下一层的走向又要取决于再下一层上的最大值是否已经求出才能决策。
  15. 这样一层一层推下去,直到倒数第二层时就非常明了。
  16. 如数字2,只要选择它下面较大值的结点19前进就可以了。
  17. 所以实际求解时,可从底层开始,层层递进,最后得到最大值。
  18. 总结:此题是最为基础的动态规划题目,阶段、状态的划分一目了然。
  19. 而决策的记录,充分体现了动态规划即“记忆化搜索”的本质。
  20. */
  21. #include <iostream>
  22. #define MAX 20
  23. using namespace std;
  24. int main()
  25. {
  26. cout << "Please input N(lines)" << endl;
  27. int n;
  28. cin >> n;
  29. int a[MAX+1][MAX+1][3]; //[0]用来存数,[1]参与运算,[2]表示向左(0),还是向右(1)
  30. //输入数塔
  31. for(int i = 1; i <= n; ++i)
  32. {
  33. cout << "Please input line " << i << endl;
  34. for(int j = 1; j <= i; ++j) //第i行有i个数
  35. {
  36. cin >> a[i][j][0];
  37. a[i][j][1] = a[i][j][0];
  38. a[i][j][2] = 0;
  39. }
  40. }
  41. cout << endl;
  42. //计算
  43. for(int i = n-1; i >= 1; --i) //从倒数第二行开始
  44. {
  45. for(int j=1; j <= i; j++)
  46. {
  47. if (a[i+1][j][1] > a[i+1][j+1][1]) //左边大
  48. {
  49. a[i][j][2] = 0; //选择左边
  50. a[i][j][1] += a[i+1][j][1];
  51. }
  52. else //右边大
  53. {
  54. a[i][j][2] = 1; //选择右边
  55. a[i][j][1] += a[i+1][j+1][1];
  56. }
  57. }
  58. }
  59. //输出数塔
  60. for(int i = 1; i <= n; ++i)
  61. {
  62. for(int j = 1; j <= i; ++j)
  63. {
  64. cout << a[i][j][0] << " ";
  65. }
  66. cout << endl;
  67. }
  68. //输出最大值
  69. cout << a[1][1][1] << endl;
  70. //输出路径
  71. for(int i = 1, j = 1; i<= n; ++i)
  72. {
  73. cout << "[" << i << "," << j << "]" << " -> ";
  74. j += a[i][j][2];
  75. }
  76. cout << endl;
  77. return 0;
  78. }

 

最简:
#include <stdio.h>


int max(int x,int y){return x>y?x:y;}


int main()
{
   int sum,i,j,a[101][101],c,n;
   scanf("%d",&c);
   while(c--)
   {
	   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-1][j],a[i][j+1]+a[i-1][j]);
		   }
	   }
	   printf("%d\n",a[0][0]);
   }
	return 0;
}

前几天做了好几个DP题目,感觉都是一个类型的,因此有必要总结一下。

 

数塔问题 :要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?

分析:站在位置9,我们可以选择沿12方向移动,也可以选择沿着15方向移动,现在我们假设“已经求的”沿12方向的最大值x和沿15方向的最大值y,那么站在9的最大值必然是:Max(x,y) + 9。

因此不难得出,对于任意节点i,其状态转移方程为:m[i] = Max(a[i的左孩子] , a[i的右孩子]) + a[i]

复制代码
     
     
#include < stdio.h > #define N 10000 #define Max(a,b) ((a) > (b) ? (a) : (b)) int a[N]; int main( void ) { int n , m , i , k , j; scanf( " %d " , & m); while (m -- > 0 ) { scanf( " %d " , & n); k = ( 1 + n) * n / 2 ; for (i = 1 ; i <= k; i ++ ) { scanf( " %d " ,a + i); } k = k - n; for (i = k , j = 0 ; i >= 1 ; i -- ) { a[i] = a[i] + Max(a[i + n],a[i + n - 1 ]); if ( ++ j == n - 1 ) { n -- ; j = 0 ; } } printf( " %d\n " ,a[ 1 ]); } return 0 ; }
复制代码

首先什么是“数塔类型”?从某一点转向另一点或者说是从某一状态转向另一状态,有多种选择方式(比如这里的9->12 , 9->15),从中选取一条能产生最优值的路径。

这类问题的思考方法:假设后续步骤的结果已知,比如这里假设已经知道沿12方向的最大值x和沿15方向的最大值y。

接下来看几个题,加深印象吧

 

1.免费馅饼问题

                     5 (起始位置)

     4       |      5       |       6

3   4   5  |  4   5   6  |  5   6   7

..................

和“数塔”一样,它也是从某一点出发,有多个选择的问题(往前走一步,呆在原地,往后走一步)从中选择一条最优值路径(获得馅饼最多)。还是按照“数塔”的思考方式,我们可以假设“已经求得”下一个站在位置4获得的最大值x和呆在原地获得的最大值y以及站在位置6获得的最大值z,那么对于起始位置5获得最大值就是Max(x,y,z) ,因此可以得到状态转移方程为:m[t][x] = Max(m[t+1][x-1] , m[t+1][x] , m[t+1][x+1])

并且我们可以通过“列表格”的方式,自底向上求解:

复制代码
     
     
#include < stdio.h > #include < string .h > #define N 100000 int a[N][ 11 ];
int Max( int a , int b , int c) { int n; n = a > b ? a : b; return n > c ? n : c; } int main( void ) { int n , x , t , max , i; while (scanf( " %d " , & n)) { if ( ! n) break ; max = 0 ; memset(a , 0 , sizeof (a)); for (i = 0 ; i < n ; i ++ ) { scanf( " %d%d " , & x, & t); a[t][x] += 1 ; if (t > max) max = t; } // DP for (t = max - 1 ; t >= 0 ; t -- ) { a[t][ 0 ] += Max( 0 , a[t + 1 ][ 0 ] , a[t + 1 ][ 1 ]) ; for (x = 1 ; x < 10 ; x ++ ) { a[t][x] += Max(a[t + 1 ][x - 1 ] , a[t + 1 ][x] , a[t + 1 ][x + 1 ]) ; } a[t][ 10 ] += Max(a[t + 1 ][ 9 ] , a[t + 1 ][ 10 ] , 0 ) ; } printf( " %d\n " ,a[ 0 ][ 5 ]); } return 0 ; }
复制代码

2.滑雪问题

      上

左   A    右

  下

依然和“数塔”一样,从某一点出发,面临多个选择(往上,往左,往下,往右)从中选择一条最优值路径(滑雪距离最长)

若对A点求,很显然它的最大值就为: Max(上,右,下,左) + 1

因此对于任意位置[i,j], 其状态转移方程为:m[i][j] = Max(m[i-1][j] , m[i][j+1] , m[i+1][j] , m[i][j-1]) + 1

由于这道题很难画出它的路径图(起点和终点都不知道)因此很难用“列表格”的方式自底向上求解,因此我们采用备忘录法:

代码

3.Worm问题,这题和免费馅饼几乎是一样的,我们同样可以使用“列表格”的方式自底向上求解:

复制代码
     
     
#include < stdio.h > #include < string .h > #define N 100 int a[N][N]; int main( void ) { int t , x , n , p , m , T; while (scanf( " %d%d%d%d " , & n, & p, & m, & T) != EOF) // 苹果树n,毛毛虫其实位置p,m分钟,终点位置T { memset(a, 0 , sizeof (a)); a[m][T] = 1 ; for (t = m - 1 ; t >= 0 ; t -- ) { a[t][ 1 ] += a[t + 1 ][ 2 ]; for (x = 2 ; x < n ; x ++ ) a[t][x] += a[t + 1 ][x - 1 ] + a[t + 1 ][x + 1 ]; a[t][n] += a[t + 1 ][n - 1 ]; } printf( " %d\n " , a[ 0 ][p]); } return 0 ; }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值