数塔
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的题目,你能AC吗?
Input
Output
Sample Input
1 5 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5
Sample Output
30
- 数塔问题:
- 9
- 12 15
- 10 6 8
- 2 18 9 5
- 19 7 10 4 16
- 有形如图所示的数塔,从顶部出发,在每一结点可以选择向左走或是向右走,
- 一直走到底层,要求找出一条路径,使路径上的值最大。
- 这道题如果用枚举法,在数塔层数稍大的情况下(如40),则需要列举出的路径条数将是一个非常庞大的数目。
- 如果用贪心法又往往得不到最优解。
- 在用动态规划考虑数塔问题时可以自顶向下的分析,自底向上的计算。
- 从顶点出发时到底向左走还是向右走应取决于是从左走能取到最大值还是从右走能取到最大值,
- 只要左右两道路径上的最大值求出来了才能作出决策。
- 同样的道理下一层的走向又要取决于再下一层上的最大值是否已经求出才能决策。
- 这样一层一层推下去,直到倒数第二层时就非常明了。
- 如数字2,只要选择它下面较大值的结点19前进就可以了。
- 所以实际求解时,可从底层开始,层层递进,最后得到最大值。
- 总结:此题是最为基础的动态规划题目,阶段、状态的划分一目了然。
- 而决策的记录,充分体现了动态规划即“记忆化搜索”的本质。
- */
- #include <iostream>
- #define MAX 20
- using namespace std;
- int main()
- {
- cout << "Please input N(lines)" << endl;
- int n;
- cin >> n;
- int a[MAX+1][MAX+1][3]; //[0]用来存数,[1]参与运算,[2]表示向左(0),还是向右(1)
- //输入数塔
- for(int i = 1; i <= n; ++i)
- {
- cout << "Please input line " << i << endl;
- for(int j = 1; j <= i; ++j) //第i行有i个数
- {
- cin >> a[i][j][0];
- a[i][j][1] = a[i][j][0];
- a[i][j][2] = 0;
- }
- }
- cout << endl;
- //计算
- for(int i = n-1; i >= 1; --i) //从倒数第二行开始
- {
- for(int j=1; j <= i; j++)
- {
- if (a[i+1][j][1] > a[i+1][j+1][1]) //左边大
- {
- a[i][j][2] = 0; //选择左边
- a[i][j][1] += a[i+1][j][1];
- }
- else //右边大
- {
- a[i][j][2] = 1; //选择右边
- a[i][j][1] += a[i+1][j+1][1];
- }
- }
- }
- //输出数塔
- for(int i = 1; i <= n; ++i)
- {
- for(int j = 1; j <= i; ++j)
- {
- cout << a[i][j][0] << " ";
- }
- cout << endl;
- }
- //输出最大值
- cout << a[1][1][1] << endl;
- //输出路径
- for(int i = 1, j = 1; i<= n; ++i)
- {
- cout << "[" << i << "," << j << "]" << " -> ";
- j += a[i][j][2];
- }
- cout << endl;
- return 0;
- }
最简:
#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 ; }