根据《算法竞赛进阶指南》,数字三角形问题描述为“给定一个共有N行三角矩阵A, 其中有第i行第j列,从左上角出发,每次可以向下方或右下方走一步,最终到达底部。求求把经过的所有位置上的数加起来,和最大是多少”。
下面看一个模拟上述过程的简单模板题(来源usaco training)
题目:
给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输入格式
第一行包含整数 n,表示数字三角形的层数。
接下来 n 行,每行包含若干整数,其中第 i 行表示数字三角形第 i 层包含的整数。
输出格式
输出一个整数,表示最大的路径数字和。
数据范围
1 ≤ n ≤ 500
−10000 ≤ 三角形中的整数 ≤ 10000
输入样例:
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
解析:
数字三角形本质是一个DP(动态规划)问题,所以我们要用动态规划的思想解决问题。
那么,DP思路步骤是怎么样的呢?
首先,我们要明确状态表示。此题我们用 f [ i ] [ j ] 表示走到第 i 行第 j 列时,数字的和最大值。比如 f [ 3 ] [ 2 ] 就是走到第三行第二列时,此时所走路径和的最大值。那么只要我们从上往下求出每个第行每列的值,根据题目要求,从最后一行,即第 n 行中取出最大值即为答案。
其次,就是状态转移方程。状态表示我们弄出来了,问题是每个状态怎么推导出来,这就是状态转移方程的作用,由题意可得,每个结点可以由上面或左上方结点走到,也就是说,第 i 行第 j 列的
f [ i ] [ j ] 可以是 f [i - 1][ j - 1] + 当前结点值 或者 是 f[ i - 1][ j ] + 当前结点值,因为是最大值,那么推出状态转移方程为 f [ i ][ j ] = max ( f[ i - 1 ][ j - 1] , f[ i - 1] [ j ] ) + 当前结点值。剩下细节看代码实现。
#include <bits/stdc++.h>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, w[N][N], f[N][N];
int main()
{
cin >> n;
//先输入每个位置数值, 因为是三角形, 每行的列数j <= 行数 i
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= i; j ++) scanf("%d", &w[i][j]);
//这里f[i][j]先初始化为负无穷
// 如果不初始化为-INF, 举个例子
// 列
// 行 0 1 2 3
// 0 0 0 0 0
// 1 0 -4 0 0
// 2 0 -2 -1 0
// 3 0 -1 -2 -3
//因为全局变量默认为0, 那么输入上述值, 三角形呈上图形式,
// 根据状态转移方程 f[i][j] = max(f[i - 1][j - 1] + f[i - 1][j]) + w[i][j],
// f[2][1]的结果就是错误的,为什么?因为f[2][1] = max(f[1][0], f[1][1]) + w[2][1], 而f[1][0] == 0, 大于f[1][1],
// 那么根据方程, f[2][2] = f[1][0] + w[2][1], 然而这时三角形,f[1][0]根本不存在, 这是错误的, 所以先都初始化为负无穷
for (int i = 0; i <= n; i ++)
for (int j = 0; j <= i + 1; j ++) f[i][j] = -INF;
//f[1][1]必须先赋值, 因为f[1][1]上面“没有”了,如果从1开始循环, f[1][1] 必然等于 负无穷 + w[1][1], 显然错误。
f[1][1] = w[1][1];
for (int i = 2; i <= n; i ++)
for (int j = 1; j <= i; j ++) f[i][j] = w[i][j] + max(f[i - 1][j - 1], f[i - 1][j]);
//最后一行每个取最大值即可
int res = -INF;
for (int i = 1; i <= n; i ++) res = max(res, f[n][i]);
cout << res << endl;
return 0;
}