数塔问题


1.onlineJ43:数塔问题

有一个由数字组成的三角形数塔,站在上一层的某个点,只能到达其下方左右的两个点。现在请找到一条从上到下的路径,使得路径上所有数字相加之和最大

image-20210417201534724

1.递推法从上向下求和

  1. 由数塔中到达任意一点的数字和sum = 左上方数字和suml or 右上方数字和sumr + 该数字数值num
  2. 知到达该点最大和为ans[x][y] = max(ans[x - 1][y - 1], ans[x - 1][y]) + num[x][y]==关键==
  3. 遍历最后一行,找出最大的数字即为结果

image-20210418110447750

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>
#include<cstdio>
using namespace std;

int n;
int num[1005][1005];

int main(){
scanf("%d", &n);
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= i; ++j){
scanf("%d", &num[i][j]);
}
}
//1.从两个方向取数值大的方向max(num[i - 1][j - 1], num[i - 1][j]);
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= i; ++j){
num[i][j] += max(num[i - 1][j - 1], num[i - 1][j]);
}
}
int ans = 0;
//2.遍历最后一行,找出最大的数字即为结果
for(int i = 1; i <= n; ++i){
ans = max(ans, num[n][i]);
}
printf("%d\n", ans);
return 0;
}

2.递推法从下向上求和

  1. 由数塔中到达任意一点的数字和sum = 左下方数字和suml or 右下方数字和sumr + 该数字数值num,
  2. 知到达该点最大和为num[x][y] = max(num[x + 1][y], num[x + 1][y + 1]) + num[x][y]==关键==
  3. 对整个数塔处理后,最大值即为(1, 1)点处的数值

image-20210418110523588

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<iostream>
#include<cstdio>
using namespace std;

int n, num[1005][1005];

int main(){
scanf("%d", &n);
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= i; ++j){
scanf("%d", &num[i][j]);
}
}
//1.从两个方向取数值大的方向max(num[x + 1][y], num[x + 1][y + 1]);
for(int i = n; i > 0; --i){
for(int j = 1; j <= i; ++j){
num[i][j] += max(num[i + 1][j], num[i + 1][j + 1]);
}
}
//2.对整个数塔处理后,最大值即为(1, 1)点处的数值
printf("%d\n", num[1][1]);
return 0;
}

2.onlineJ590:数塔狂想曲

如下图是一个数塔,映射到该数塔上行走的规则为:从左上角的点开始,向下走或向右下走直到最底层结束。

1
2
3
4
5
1
3 8
2 5 0
1 4 3 8
1 4 2 5 0

路径最大和是 1+8+5+4+4=221+8+5+4+4=22,1+8+5+3+5=221+8+5+3+5=22 或者 1+8+0+8+5=221+8+0+8+5=22

小 S 觉得这个问题 so easy,于是他提高了点难度,他每次 ban 掉一个点,然后询问你不走该点的最大路径和。当然他上一个询问被 ban 掉的点过一个询问就会恢复。

如果被 ban 掉后不存在任意一条路径,则输出 −1。

1.利用最大值、次大值输出预处理答案

  1. 利用数塔问题的两种解法,从上向下和、从下向上和可以求出经过某点(x, y)最大数字和数组
  2. 找到每一行的最大值(记录下标)、次大值(记录数值)
  3. 对于某一次询问(x, y),判断被ban的值是否为最大值;如果不是输出最大值,否则直接输出次大值

总述:将数塔输入并存储在二维数组中,利用数塔问题的两种解法将绿色、蓝色二维数组求出

求出黑色二维数组(经过某点所能获得的最大和),最后通过记录最大值、次大值来求出结果

image-20210418153351294

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include<iostream>
#include<cstdio>
using namespace std;

//n:数塔行数、m:询问次数、utd:从上向下求解的数组、dtu:从下向上求解的数组、ans:经过某点最大和数组
int n, m, num[1005][1005], utd[1005][1005], dtu[1005][1005], ans[1005][1005];
//mmax记录最大值、mmax_ind记录最大值下标、mmax2记录次大值
int mmax[1005], mmax_ind[1005], mmax2[1005];

int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= i; ++j){
scanf("%d", &num[i][j]);
}
}
//1.利用数塔问题的两种解法,从上向下和、从下向上和可以求出经过某点(x, y)最大数字和数组
//(1)从上向下求和,并存入数组utd中
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= i; ++j){
utd[i][j] = max(utd[i - 1][j - 1], utd[i - 1][j]) + num[i][j];
}
}
//(2)从下向上求和,并存入数组dtu中
for(int i = n; i > 0; --i){
for(int j = 1; j <= i; ++j){
dtu[i][j] = max(dtu[i + 1][j], dtu[i + 1][j + 1]) + num[i][j];
}
}
//(3)通过从上向下和、从下向上和可以求出经过某点(x, y)最大数字和ans数组
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= i; ++j){
ans[i][j] = utd[i][j] + dtu[i][j] - num[i][j];
}
}
//2.找到每一行的最大值(记录数值、下标)、次大值(记录数值)
for(int i = 2; i <= n; ++i){
//定义临时变量m、m2、ind
int m = 0, m2 = 0, ind = 0;
for(int j = 1; j <= i; ++j){
if(m < ans[i][j]){
//(1)遍历第i行记录第i行的最大值和下标
m2 = m;//原来的最大值赋值给次大值
m = ans[i][j];
ind = j;
}else if(m2 < ans[i][j]){
//(2)遍历第i行记录第i行的次大值
m2 = ans[i][j];
}
}
mmax[i] = m, mmax_ind[i] = ind, mmax2[i] = m2;
}
//3.对于某一次询问(x, y),判断被ban的值是否为最大值;如果不是输出最大值,否则直接输出次大值
for(int i = 0; i < m; ++i){
int x, y;
scanf("%d%d", &x, &y);
if(x == 1){
printf("-1\n");
}else if(mmax_ind[x] == y){
//(1)x行的最大值被ban,输出x行的次大值mmax2
printf("%d\n", mmax2[x]);
}else{
//(2)x行的最大值未被ban,输出x行的最大值mmax
printf("%d\n", mmax[x]);
}
}
return 0;
}