三角形取数(Hard Version)
思路
拿到题先理解这个"三角形"的结构:第 行有
个数,视觉上就是一个正三角形,每往下一行两边各多一个数。
从顶点出发,每一步可以往左下、正下、右下三个方向走。记左下走了 次、右下走了
次,要求最终
,问路径上数字之和的最大值。
关键观察: 每一步的列变化量分别是 (左下不变列号,但因为下一行比当前行左边多了一个位置,相当于相对位移
)、
(正下)、
(右下)。起始在第
行正中央,设"偏移量"为
,那么到第
行时,列号
。
约束 就等价于最终列号满足
,即
。
这个观察把问题彻底简化了——约束只作用在最后一行的列号范围上,中间过程完全不需要额外状态。
所以就是一个最普通的三角形 DP:
$$
其中 对应从上一行的"左下"走过来(列号不变),
是"正下",
是"右下"。
最后答案就是 。
用滚动数组优化掉行的维度,空间 。
注意点:
- 数值范围
,路径长度最大
,总和可能到
,要用
long long。 - 初始化为极小值,只有起点
是有效状态。
代码
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
const long long NEG_INF = -4e18;
int maxcol = 2 * n;
vector<long long> dp(maxcol, NEG_INF);
long long val;
cin >> val;
dp[1] = val; // 第1行唯一的数,列号1
for(int i = 2; i <= n; i++){
int cols = 2 * i - 1;
int prevCols = 2 * (i - 1) - 1;
vector<long long> a(cols + 1);
for(int j = 1; j <= cols; j++) cin >> a[j];
vector<long long> ndp(maxcol, NEG_INF);
for(int pj = 1; pj <= prevCols; pj++){
if(dp[pj] == NEG_INF) continue;
// 左下:列号不变
if(pj <= cols)
ndp[pj] = max(ndp[pj], dp[pj] + a[pj]);
// 正下:列号+1
if(pj + 1 <= cols)
ndp[pj+1] = max(ndp[pj+1], dp[pj] + a[pj+1]);
// 右下:列号+2
if(pj + 2 <= cols)
ndp[pj+2] = max(ndp[pj+2], dp[pj] + a[pj+2]);
}
dp = ndp;
}
long long ans = NEG_INF;
for(int j = max(1, n - k); j <= min(2*n - 1, n + k); j++){
ans = max(ans, dp[j]);
}
cout << ans << "\n";
return 0;
}
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
int k = Integer.parseInt(st.nextToken());
final long NEG_INF = (long)(-4e18);
int maxcol = 2 * n;
long[] dp = new long[maxcol];
Arrays.fill(dp, NEG_INF);
st = new StringTokenizer(br.readLine());
dp[1] = Long.parseLong(st.nextToken());
for (int i = 2; i <= n; i++) {
int cols = 2 * i - 1;
int prevCols = 2 * (i - 1) - 1;
long[] a = new long[cols + 1];
st = new StringTokenizer(br.readLine());
for (int j = 1; j <= cols; j++)
a[j] = Long.parseLong(st.nextToken());
long[] ndp = new long[maxcol];
Arrays.fill(ndp, NEG_INF);
for (int pj = 1; pj <= prevCols; pj++) {
if (dp[pj] == NEG_INF) continue;
if (pj <= cols)
ndp[pj] = Math.max(ndp[pj], dp[pj] + a[pj]);
if (pj + 1 <= cols)
ndp[pj+1] = Math.max(ndp[pj+1], dp[pj] + a[pj+1]);
if (pj + 2 <= cols)
ndp[pj+2] = Math.max(ndp[pj+2], dp[pj] + a[pj+2]);
}
dp = ndp;
}
long ans = NEG_INF;
for (int j = Math.max(1, n - k); j <= Math.min(2*n - 1, n + k); j++)
ans = Math.max(ans, dp[j]);
System.out.println(ans);
}
}
import sys
input = sys.stdin.readline
def main():
n, k = map(int, input().split())
NEG_INF = float('-inf')
maxcol = 2 * n
dp = [NEG_INF] * maxcol
val = int(input().split()[0])
dp[1] = val
for i in range(2, n + 1):
cols = 2 * i - 1
prev_cols = 2 * (i - 1) - 1
a = [0] + list(map(int, input().split()))
ndp = [NEG_INF] * maxcol
for pj in range(1, prev_cols + 1):
if dp[pj] == NEG_INF:
continue
if pj <= cols:
v = dp[pj] + a[pj]
if v > ndp[pj]:
ndp[pj] = v
if pj + 1 <= cols:
v = dp[pj] + a[pj + 1]
if v > ndp[pj + 1]:
ndp[pj + 1] = v
if pj + 2 <= cols:
v = dp[pj] + a[pj + 2]
if v > ndp[pj + 2]:
ndp[pj + 2] = v
dp = ndp
ans = NEG_INF
lo = max(1, n - k)
hi = min(2 * n - 1, n + k)
for j in range(lo, hi + 1):
if dp[j] > ans:
ans = dp[j]
print(ans)
main()
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin });
const lines = [];
rl.on('line', l => lines.push(l));
rl.on('close', () => {
const [n, k] = lines[0].split(' ').map(Number);
const NEG_INF = -4e18;
const maxcol = 2 * n;
let dp = new Array(maxcol).fill(NEG_INF);
dp[1] = parseInt(lines[1]);
for (let i = 2; i <= n; i++) {
const cols = 2 * i - 1;
const prevCols = 2 * (i - 1) - 1;
const parts = lines[i].split(' ').map(Number);
const a = [0, ...parts];
const ndp = new Array(maxcol).fill(NEG_INF);
for (let pj = 1; pj <= prevCols; pj++) {
if (dp[pj] === NEG_INF) continue;
if (pj <= cols)
ndp[pj] = Math.max(ndp[pj], dp[pj] + a[pj]);
if (pj + 1 <= cols)
ndp[pj+1] = Math.max(ndp[pj+1], dp[pj] + a[pj+1]);
if (pj + 2 <= cols)
ndp[pj+2] = Math.max(ndp[pj+2], dp[pj] + a[pj+2]);
}
dp = ndp;
}
let ans = NEG_INF;
for (let j = Math.max(1, n - k); j <= Math.min(2*n - 1, n + k); j++) {
ans = Math.max(ans, dp[j]);
}
console.log(ans);
});
复杂度分析
- 时间复杂度:
。每行最多
个元素,遍历
行,总操作量
。
- 空间复杂度:
。滚动数组只保留一行的 DP 值。
小结
这道题看着是三角形路径求最大和的"进阶版"——多了左右移动次数差的限制。但仔细分析就会发现,起始位置固定在正中央,每走一步左下/右下分别会让"偏移量"减一/加一,而直走不变偏移。到最后一行时偏移量就等于 ,直接反映在列号上。所以
这个约束根本不需要额外维度来追踪,只需要在最后一行限制取答案的列号范围即可。整道题本质上就是一个标准的三角形 DP,
轻松搞定。



京公网安备 11010502036488号