题目链接:https://www.acwing.com/problem/content/900/
给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输入格式
第一行包含整数 nn,表示数字三角形的层数。
接下来 nn 行,每行包含若干整数,其中第 ii 行表示数字三角形第 ii 层包含的整数。
输出格式
输出一个整数,表示最大的路径数字和。
数据范围
1≤n≤500,
−10000≤三角形中的整数≤10000
输入样例:
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出样例:
30
普通思路:
以下称''最大值''为题设要求:从顶点到某一点的最大路径和,''值''为当前点的权值。
存储:采用二维数组,简化思维。使用二维数组,将所有的元素以靠左的方式存储,即像输入样例中的位置一般。即三角形中某一点的下方左右分支,变成了正下方和右下方分支。则可以另开一个与之相同大小的二维数组,用来存储当前点(该数组中某一位置点),从三角形顶部到当前位置的最大值,即可。
(遍历是查看当前点能从什么地方来,即正上方和左上方,在没有发生越界的情况下,拿出来源点的最大值(从顶点到该点的最大值),将两个来源点(可能只有一个)进行比较,采用值大的那一个,并加上当前位置的值,即从顶点到该点的最大值。)
只需要按照三角形遍历一遍,得到三角形每个点从顶点到该位置的最大值,最后在遍历一遍三角形的最后一行,即可求出题目要求的值。
优化空间:
如果采用二维数组来存,那么会浪费将近一倍的空间。因为采用矩阵来存储,必将会有空位。于是考虑能否使用一维数组来将其存储。
1.首先,应该算出该一维数组所需大小,不难看出这个三角形是一个等差数列,从个数为
count=(1+n)*n/2
2.然后,先对此三角形的点位置进行排列
1
2 3
4 5 6
7 8 9 10
………………………………………………
如果采用当前编号存储某个元素,还有一个问题,就是如何得到当前点的来源点。
于是对三角形进行编层次。
1 -------层次 1
2 3 ----------2
4 5 6 ----------3
7 8 9 10 ----------4
………………………………………………
如果拿当前点的编号减去层值,会是什么效果呢。例如编号9,在第4层,9-4=5,发现这是编号9的左上方来源编号。如果再+1,则会得到来源右上方的编号。
现在来看特殊点的。如果编号为1或者2,4,7……或者3,6,10……即三角形的上方两条边,将其编号减去层的时候会出现一些不应该出现的越界问题。
于是在减去层值的时候还要判断越界问题。那么当前编号必须在范围内(按图上应该是[1,10])并且得到的编号的层值是该点层值-1。什么意思呢,就是找来源点的时候,得到的点必须应该是上一层的。
于是找点的问题也解决了。
3.和之前的思路一直,再开一个相同大小的数组,然后遍历得到最大值,最后遍历最后一层的最大值就行了。因为在输入数据的时候会输入三角形的层级n,层级也就代表着最后一层有多少个。直接从末尾往前遍历n个,取出最大值即可。
在这里我就采用结构体数组的形式。
先是注释版本:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const LL N = 1e7 + 100;
const LL INF = -0x3f3f3f3f;
// 存放该三角形的一维数组
struct node {
LL deep, value, max_value;
// deep:当前点的层数
// value: 当前点的值
// max_value:当前点的最大值
} arr[N];
int main() {
LL n;cin >> n;
LL count = (1 + n) * n / 2; // 求出需要使用多少个数组元素
// i:输入每一个三角形点到一维数组中
// deep: 当前的层数
// idx:记录当前的层的个数,即在存放的过程中存储当前这一层出现了多少个
// 如果当前和层数一样了那么表示这一层结束了,deep需要 ++,到下一层
for (LL i = 0, idx = 1, deep = 1;i < count;i ++, idx ++) {
cin >> arr[i].value;
arr[i].deep = deep; // 记录层值
if (idx == deep) idx = 0, deep ++;
}
// 现在输入完成之后继续遍历数组,得到每一个点的当前最大值
for (LL i = 0, idx = 1, temp = 0;i < count;i ++) {
// 我先得到了deep - 1这个数值, 我是先取得右上方的点,再取左上方的点
// 当然,你也可以按照你的顺序
LL t = i - (arr[i].deep - 1);
// 因为当前有可能出现越界的情况,我先定义两个变量,分别代表着左上方和右上方对应的最大值
// 如果说上方的点是可以访问的,那么再下面会为其覆盖值。
// 如果依旧等于INF,那么就是一个越界情况,就会跳过改点
LL t_1 = INF, t_2 = INF;
// 如果右上方的编号是合法的,因为是数组需要 > 0
// 并且深度为当前点的深度 - 1,即为必须是在其上方,那么就是一个合法点,赋值给t_1
if (t >= 0 && arr[t].deep == arr[i].deep - 1) t_1 = arr[t].max_value;
// 如果左上方编号合法,赋值给t_2
if (t - 1 >= 0 && arr[t - 1].deep == arr[i].deep - 1) t_2 = arr[t - 1].max_value;
// 如果说其中有一个是合法的,那么得到其中的最大值,加上当前点的值
// 因为INF是一个为该题基本不会到达的一个负数,所以直接取最大值是可行的
if (t_1 != INF || t_2 != INF) arr[i].max_value = max(t_1, t_2) + arr[i].value;
// 如果都有问题,也就是没有左右上方点,那么当前点的最大值就是改点的值
else arr[i].max_value = arr[i].value;
}
LL res = INF;
// 最后打印最大值,用idx计数,共计n个,用i来作为数组下标
for (LL i = count - 1, idx = 0;idx < n;idx ++) res = max(res, arr[i --].max_value);
cout << res;
return 0;
}
然后是简洁版本:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const LL N = 1e7 + 100;
const LL INF = -0x3f3f3f3f;
struct node {
LL deep, value, max_value;
} arr[N];
int main() {
LL n;cin >> n;
LL count = (1 + n) * n / 2;
for (LL i = 0, idx = 1, tmp = 1;i < count;i ++, idx ++) {
cin >> arr[i].value;
arr[i].deep = tmp;
if (idx == tmp) idx = 0, tmp ++;
}
for (LL i = 0, idx = 1, temp = 0;i < count;i ++) {
LL t = i - (arr[i].deep - 1);
LL t_1 = INF, t_2 = INF;
if (t >= 0 && arr[t].deep == arr[i].deep - 1) t_1 = arr[t].max_value;
if (t - 1 >= 0 && arr[t - 1].deep == arr[i].deep - 1) t_2 = arr[t - 1].max_value;
if (t_1 != INF || t_2 != INF) arr[i].max_value = max(t_1, t_2) + arr[i].value;
else arr[i].max_value = arr[i].value;
}
LL res = INF;
for (LL i = count - 1, idx = 0;idx < n;idx ++) res = max(res, arr[i --].max_value);
cout << res;
return 0;
}