数塔问题 :要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?
分析:站在位置9,我们可以选择沿12方向移动,也可以选择沿着15方向移动,现在我们假设“已经求的”沿12方向的最大值x和沿15方向的最大值y,那么站在9的最大值必然是:Max(x,y) + 9。
因此不难得出,对于任意节点i,其状态转移方程为:m[i] = Max(a[i的左孩子] , a[i的右孩子]) + a[i]
1 #include <iostream> 2 using namespace std; 3 4 #define Max(a,b) ((a)>(b) ? (a):(b)) 5 6 int a[1000]; 7 8 int main(){ 9 int t,n,i,k,j,m; 10 cin>>t; 11 for(i=0;i<t;i++){ 12 13 cin>>n; 14 k=n*(n+1)/2; 15 for(j=1;j<=k;j++){ 16 cin>>a[j]; 17 } 18 k = k - n; //从下往上 从倒数第二行最后一个元素开始依次找到其到最后一行的最大路径值 一直找到第一行第一个元素即是最大值 19 for(i=k,j=0;i>=1;i--){ 20 a[i]=a[i]+Max(a[i+n],a[i+n-1]); 21 if(++j==n-1) 22 {n--; 23 j=0;} 24 } 25 cout<<a[1]<<endl; 26 27 } 28 return 0; 29 }
首先什么是“数塔类型”?从某一点转向另一点或者说是从某一状态转向另一状态,有多种选择方式(比如这里的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])
并且我们可以通过“列表格”的方式,自底向上求解:
1 代码 2 3 #include <stdio.h> 4 #include <string.h> 5 6 #define N 100000 7 int a[N][11]; 8 9 int Max(int a , int b , int c) 10 { 11 int n; 12 13 n = a > b ? a : b; 14 15 return n > c ? n : c; 16 } 17 18 19 int main(void) 20 { 21 int n , x , t , max , i; 22 23 24 while(scanf("%d",&n)) 25 { 26 if(!n) break; 27 28 max = 0; 29 memset(a , 0 , sizeof(a)); 30 for(i = 0 ; i < n ; i++) 31 { 32 scanf("%d%d",&x,&t); 33 a[t][x] += 1; 34 if(t > max) max = t; 35 } 36 37 38 //DP 39 for(t = max - 1 ; t >= 0 ; t--) 40 { 41 a[t][0] += Max(0 , a[t + 1][0] , a[t + 1][1]) ; 42 43 for(x = 1 ; x < 10 ; x++) 44 { 45 a[t][x] += Max(a[t + 1][x - 1] , a[t + 1][x] , a[t + 1][x + 1]) ; 46 } 47 a[t][10] += Max(a[t + 1][9] , a[t + 1][10] , 0) ; 48 } 49 printf("%d ",a[0][5]); 50 } 51 52 return 0; 53 }
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
由于这道题很难画出它的路径图(起点和终点都不知道)因此很难用“列表格”的方式自底向上求解,因此我们采用备忘录法:
1 代码 2 3 #include <stdio.h> 4 #include <string.h> 5 #define N 101 6 7 int a[N][N] , m[N][N] , r , c; 8 9 int OK(int i ,int j) 10 { 11 return (i >= 1 && i <= r && j >= 1 && j <= c); 12 } 13 14 int search(int i , int j) 15 { 16 int k; 17 18 if(m[i][j] > 0) return m[i][j]; 19 20 if(OK(i - 1, j) && a[i][j] > a[i - 1][j]) 21 { 22 k = search(i - 1, j) + 1; 23 m[i][j] = m[i][j] < k ? k : m[i][j]; 24 } 25 if(OK(i, j + 1) && a[i][j] > a[i][j + 1]) 26 { 27 k = search(i, j + 1) + 1; 28 m[i][j] = m[i][j] < k ? k : m[i][j]; 29 } 30 if(OK(i + 1, j) && a[i][j] > a[i + 1][j]) 31 { 32 k = search(i + 1, j) + 1; 33 m[i][j] = m[i][j] < k ? k : m[i][j]; 34 } 35 if(OK(i, j - 1) && a[i][j] > a[i][j - 1]) 36 { 37 k = search(i, j - 1) + 1; 38 m[i][j] = m[i][j] < k ? k : m[i][j]; 39 } 40 41 return m[i][j] ; 42 } 43 44 int main(void) 45 { 46 int i , j , k , t; 47 48 while(scanf("%d%d", &r, &c) != EOF) 49 { 50 for(i = 1 ; i <= r ; i++) 51 for(j = 1 ; j <= c ; j++) 52 scanf("%d", &a[i][j]); 53 54 memset(m, 0, sizeof(m)); k = 0; 55 for(i = 1 ; i <= r ; i++) 56 for(j = 1 ; j <= c ; j++) 57 { 58 t = search(i, j); 59 k = k < t ? t : k; 60 } 61 62 printf("%d ", k + 1); 63 64 } 65 return 0; 66 }
3.Worm问题,这题和免费馅饼几乎是一样的,我们同样可以使用“列表格”的方式自底向上求解:
1 代码 2 3 #include <stdio.h> 4 #include <string.h> 5 #define N 101 6 7 int a[N][N] , m[N][N] , r , c; 8 9 int OK(int i ,int j) 10 { 11 return (i >= 1 && i <= r && j >= 1 && j <= c); 12 } 13 14 int search(int i , int j) 15 { 16 int k; 17 18 if(m[i][j] > 0) return m[i][j]; 19 20 if(OK(i - 1, j) && a[i][j] > a[i - 1][j]) 21 { 22 k = search(i - 1, j) + 1; 23 m[i][j] = m[i][j] < k ? k : m[i][j]; 24 } 25 if(OK(i, j + 1) && a[i][j] > a[i][j + 1]) 26 { 27 k = search(i, j + 1) + 1; 28 m[i][j] = m[i][j] < k ? k : m[i][j]; 29 } 30 if(OK(i + 1, j) && a[i][j] > a[i + 1][j]) 31 { 32 k = search(i + 1, j) + 1; 33 m[i][j] = m[i][j] < k ? k : m[i][j]; 34 } 35 if(OK(i, j - 1) && a[i][j] > a[i][j - 1]) 36 { 37 k = search(i, j - 1) + 1; 38 m[i][j] = m[i][j] < k ? k : m[i][j]; 39 } 40 41 return m[i][j] ; 42 } 43 44 int main(void) 45 { 46 int i , j , k , t; 47 48 while(scanf("%d%d", &r, &c) != EOF) 49 { 50 for(i = 1 ; i <= r ; i++) 51 for(j = 1 ; j <= c ; j++) 52 scanf("%d", &a[i][j]); 53 54 memset(m, 0, sizeof(m)); k = 0; 55 for(i = 1 ; i <= r ; i++) 56 for(j = 1 ; j <= c ; j++) 57 { 58 t = search(i, j); 59 k = k < t ? t : k; 60 } 61 62 printf("%d ", k + 1); 63 64 } 65 return 0; 66 }