数字三角形
题目描述:给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
数据范围: \(1 \leq n \leq 500\)\(-10000 \leq 三角形中的整数 \leq 10000\)
状态表示及计算
- 从正上方转移过来, 或者从左斜上方转移过来:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1]) + mp[i][j]
初始化的细节
- 状态转移过程中可能会用到边界状态同时数据中存在负数, 因此初始化时将两边也初始化成INF(INF取-0x3f3f3f3f)
代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 510, INF = -0x3f3f3f3f;
int dp[N][N], a[N][N];
int n;
int main() {
cin >> n;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= i; j++)
scanf("%d", &a[i][j]);
for(int i = 0; i <= n; i++)
for(int j = 0; j <= i + 1; j++)
dp[i][j] = INF;
dp[1][1] = a[1][1];
for(int i = 2; i <= n; i++)
for(int j = 1; j <= i; j++)
dp[i][j] = max(dp[i - 1][j] + a[i][j], dp[i - 1][j - 1] + a[i][j]);
int ans = INF;
for(int i = 1; i <= n; i++) ans = max(ans, dp[n][i]);
cout << ans;
return 0;
}
倒推法
- 从最底层开始往上推:
\[dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + mp[i][j]
\]
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510;
int n;
int a[N][N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j];
for (int i = n - 1; i >= 1; i--)
for (int j = 1; j <= i; j++)
a[i][j] = max(a[i + 1][j] + a[i][j], a[i + 1][j + 1] + a[i][j]);
cout << a[1][1];
return 0;
}
有关数字三角形问题的拓展
摘花生: 题目链接
- 注意问题:初始化,dp数组应将用到的所有的初始状态初始化, 例如
dp[0][i]
等边界状态
最低通行费用:题目链接
2n - 1
表示只能前进不能后退- 初始化:第一行、第一列的状态确定:第一行的只能从左边转移过来, 第一列只能从上面转移过来
dp[1][1] = mp[1][1];
for(int i = 1; i <= n; i++) dp[i][1] = dp[i - 1][1] + mp[i][1];
for(int i = 1; i <= n; i++) dp[1][i] = dp[1][i - 1] + mp[1][i];
for(int i = 2; i <= n; i++) {
for(int j = 2; j <= n; j++) {
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + mp[i][j];
}
}
传纸条:题目链接
分析:
- 走两次,用dp[i1,j1, i2, j2]表示分别走到(i1, j1)(i2, j2)的路径的最大值
- 当两个格子被重复走的时候,发现
i1 + j1 == i2 + j2
,但是当i1 + j1 == i2 + j2
满足时,两次走法不一定经过同一个格子 - 枚举四个维度
for(int i = 1; i <= T; i++) {
for(int j = 1; j <= T; j++) {
for(int m = 1; m <= T; m++) {
for(int n = 1; n <= T; n++) {
dp[i][j][m][n] = max(
max(dp[i][j - 1][m][n - 1], dp[i][j - 1][m - 1][n]),
max(dp[i - 1][j][m][n - 1], dp[i - 1][j][m - 1][n])
);
// 两次走重复位置,仅第一次得到该值
if(i == m && j == n) {
dp[i][j][m][n] += mp[i][j];
} else {
dp[i][j][m][n] += mp[i][j] + mp[m][n];
}
}
}
}
}
优化
- 枚举两次走的步数之和
- 当
i1 == i2
表示两次走到同一格子:两次总步数一样
for(int k = 2; k <= T * 2; k++) {
for(int i1 = 1; i1 <= T; i1++) {
for(int i2 = 1; i2 <= T; i2 ++) {
int j1 = k - i1, j2 = k - i2;
if(j1 >= 1 && j1 <= T && j2 >= 1 && j2 <= T) {
int t = mp[i1][j1];
if(i1 != i2) t += mp[i2][j2];
dp[k][i1][i2] = max(
max(dp[k - 1][i1 - 1][i2], dp[k - 1][i1][i2 - 1]),
max(dp[k - 1][i1 - 1][i2 - 1], dp[k - 1][i1][i2])
) + t;
}
}
}
}