数塔问题
- 递推与递归
- 动态规划
1.onlineJ43:数塔问题
- 原题链接:http://oj.haizeix.com/problem/43
有一个由数字组成的三角形数塔,站在上一层的某个点,只能到达其下方左右的两个点。现在请找到一条从上到下的路径,使得路径上所有数字相加之和最大
1.递推法从上向下求和
- 由数塔中到达任意一点的数字和sum = 左上方数字和suml or 右上方数字和sumr + 该数字数值num
- 知到达该点最大和为
ans[x][y] = max(ans[x - 1][y - 1], ans[x - 1][y]) + num[x][y]
关键 - 遍历最后一行,找出最大的数字即为结果
#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.递推法从下向上求和
- 由数塔中到达任意一点的数字和sum = 左下方数字和suml or 右下方数字和sumr + 该数字数值num,
- 知到达该点最大和为
num[x][y] = max(num[x + 1][y], num[x + 1][y + 1]) + num[x][y]
关键 - 对整个数塔处理后,最大值即为(1, 1)点处的数值
#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
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.利用最大值、次大值输出预处理答案
- 利用数塔问题的两种解法,从上向下和、从下向上和可以求出经过某点(x, y)最大数字和数组
- 找到每一行的最大值(记录下标)、次大值(记录数值)
- 对于某一次询问(x, y),判断被ban的值是否为最大值;如果不是输出最大值,否则直接输出次大值
总述:将数塔输入并存储在二维数组中,利用数塔问题的两种解法将绿色、蓝色二维数组求出
求出黑色二维数组(经过某点所能获得的最大和),最后通过记录最大值、次大值来求出结果
#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;
}
tips:文章部分内容参考算法刷题课程,题解图示内容及代码根据老师课程、以及自己对知识的理解,进行二次整理和部分补充,仅供学习参考使用,不可商业化。