题目链接:http://ybt.ssoier.cn:8088/problem_show.php?pid=1258
题目大意:
给你一个数字金字塔,每次可以从当前点走到左下方或右下方的点。查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。
解题思路:
设 (a_{i,j}) 表示数字金字塔上第 (i) 行从左往右数第 (j) 个点,则我们的目的就是从 (a_{1,1}) 走到第 (n) 行的 (a_{n,i})(此处 (i) 可能是 (1 sim n) 范围内的任何一个点)。
很明显这道题目可以用动态规划(DP)求解,但是根据求解的方向不同,对应的状态也不相同。
这里,我们可以以两种思路来解决这个问题:
- 第1种思路:自顶向下(从上往下推);
- 第2种思路:自底向上(从下往上推)。
下面,我们来依次分析两种思路。
自顶向下(从上往下推)
定义状态 (f_{i,j}) 表示从 (a_{1,1}) 走到 (a_{i,j}) 的所有路径数字和的最大值。则,可以推导出状态转移方程为:
- 当 (i =1) 时((i=1) 时只有一个状态 (f_{1,1})),(f_{1,1} = a_{1,1});
- 当 (i gt 1) 时,
- 当 (j = 1) 时,(f_{i,1} = f{i-1,1} + a_{i,1})(最左边的点只能从右上方走过来);
- 当 (j = i) 时,(f_{i,i} = f{i-1,i-1} + a_{i,i})(最右边的点只能从左上方走过来);
- 当 (1 lt j lt i) 时,(f_{i,j} = max{f_{i-1,j-1}f_{i-1,j}} + a_{i,j})(中间的点可以选择从左上角或右上角的点走过来,所以选择两者的较大值)
令 (i = 1 ightarrow n) 的顺序推导出所有的 (f_{i,j}),则最终的答案就是:
- (f_{n,1})(从 (a_{1,1}) 走到 (a_{n,1}) 的最大数字和)、
- (f_{n,2})(从 (a_{1,1}) 走到 (a_{n,2}) 的最大数字和)、
- (f_{n,3})(从 (a_{1,1}) 走到 (a_{n,3}) 的最大数字和)、
- …… ……
- (f_{n,n})(从 (a_{1,1}) 走到 (a_{n,n}) 的最大数字和)
的最大值,即答案为 (max{ f_{n,i} })(其中 (i in [1,n]))
说明:(in) 是“属于”符号,(i in [1,n]) 表示 (i) 属于 ([1,n]) 范围内,即 (i) 是 (1) 到 (n) 范围内(包括 (1) 和 (n))的一个整数。
按照自顶向下实现的代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
int n, a[maxn][maxn], f[maxn][maxn], ans;
int main()
{
scanf("%d", &n); // 虽然题目里用R表示行的数量,但是我还是比较习惯用n表示行数
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= i; j ++)
scanf("%d", &a[i][j]);
for (int i = 1; i <= n; i ++)
{
for (int j = 1; j <= n; j ++)
{
if (i == 1) f[i][j] = a[i][j];
else if (j == 1) f[i][j] = f[i-1][j] + a[i][j];
else if (j == i) f[i][j] = f[i-1][j-1] + a[i][j];
else f[i][j] = max(f[i-1][j-1], f[i-1][j]) + a[i][j];
}
}
for (int i = 1; i <= n; i ++)
ans = max(ans, f[n][i]);
printf("%d
", ans);
return 0;
}
自底向上(从下往上推)
从第 (1) 行的格子((a_{1,1}))走到第 (n) 行的路径的最大数字和,等价于从第 (n) 行选一个点作为起点从下往上走到 (a_{1,1}) 的路径最大数字和。所以我们可以把问题看成从下往上走找一条最大路径的数字和。
那么我们可以定义状态 (f_{i,j}) 表示:从第 (n) 行找一个点作为起点走到 (a_{i,j}) 的路径的最大数字和,则可以推导出转台转移方程为:
- 当 (i = n) 时,(f_{i,j} = a_{i,j})(因为时最下面一行,起点出发能够得到的数字就是本身);
- 当 (i lt n) 时,(f_{i,j} = max{ f_{i+1,j} , f_{i+1,j+1} } + a_{i,j})。
注意这里要按照 (i = n ightarrow 1) 的方向推导出所有的状态。
则最终的状态为 (f_{1,1})(因为从小往上推的话,所有路径对应的重点只有一个就是 (a_{1,1}))。
按照自底向上实现的代码如下(可以发现,按照这种方法实现的代码更简洁一些):
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
int n, a[maxn][maxn], f[maxn][maxn];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= i; j ++)
scanf("%d", &a[i][j]);
for (int i = n; i >= 1; i --)
{
for (int j = 1; j <= i; j ++)
{
if (i == n) f[i][j] = a[i][j];
else f[i][j] = max(f[i+1][j], f[i+1][j+1]) + a[i][j];
}
}
printf("%d
", f[1][1]);
return 0;
}