按行号和 x+y 把两条单调路径同步分层,用行号对 (x1,x2) 做二维 DP,重合格只计一次好感度,O((m+n)·m²) 求最大好感度和。
OJ: roj
题目 ID: 3564
难度:普及+/提高-
标签:动态规划
创建: 2026-10-02 08:00
更新: 2026-10-10 11:30
形式化题目
给定
回信方向(向上/向左)整体反转后与去程完全同型、覆盖格子不变,所以只需求上述两条单调路径。
样例输入 3 3 / 0 3 9 / 2 8 5 / 5 7 0,输出 34。下表展示样例的一组最优方案,路径 A 与路径 B 覆盖的格子及各自权值如下:
| 样例最优路径 | 列 1 | 列 2 | 列 3 |
|---|---|---|---|
| 行 1 | 0(两路起点) | 3(A) | 9(A) |
| 行 2 | 2(B) | 8(B) | 5(A) |
| 行 3 | 5(未使用) | 7(B) | 0(两路终点) |
路径 A 为「右→右→下→下」,权值
正解
思路
朴素做法:分别枚举两条路径再求并集和。路径条数为
瓶颈与观察:整条路径的差别只通过「当前走到哪」影响后续,所以不该枚举整条路径,而应记录位置。进一步注意到:每走一步,格子的行号与列号之和
状态:
转移:
同层若
初始化与答案:
样例的分层过程:下面两张表是样例的
| 3 | 5 | |
| 5 | 2 |
| 12 | 22 | 19 | |
| 22 | 13 | 18 | |
| 19 | 18 | 7 |
观察一次具体转移:
代码
按上述分层 DP 实现:advance 完成一层转移,solve 读入后逐层滚动并输出终点状态。
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-10 11:30
* update_at: 2026-10-10 11:30
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NEG = -1000000000;
int m, n;
int grid[55][55];
int layer[55][55], nlayer[55][55];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> m >> n;
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
cin >> grid[i][j];
for (int i = 0; i <= m; i++)
for (int j = 0; j <= m; j++)
layer[i][j] = NEG;
layer[1][1] = grid[0][0];
for (int k = 3; k <= m + n; k++) {
for (int i = 0; i <= m; i++)
for (int j = 0; j <= m; j++)
nlayer[i][j] = NEG;
int lo = max(1, k - n), hi = min(m, k - 1);
for (int x1 = lo; x1 <= hi; x1++) {
int v1 = grid[x1 - 1][k - x1 - 1];
for (int x2 = lo; x2 <= hi; x2++) {
int best = NEG;
for (int p1 = x1; p1 >= x1 - 1 && p1 >= 1; p1--)
for (int p2 = x2; p2 >= x2 - 1 && p2 >= 1; p2--)
if (layer[p1][p2] > best) best = layer[p1][p2];
if (best == NEG) continue;
int gain = v1 + (x1 != x2 ? grid[x2 - 1][k - x2 - 1] : 0);
nlayer[x1][x2] = best + gain;
}
}
for (int i = 0; i <= m; i++)
for (int j = 0; j <= m; j++)
layer[i][j] = nlayer[i][j];
}
cout << layer[m][m] << "\n";
return 0;
}#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 08:02
# update_at: 2026-10-02 08:02
import sys
NEG = -10**9 # 不可达状态的哨兵(好感度非负,可达值恒 >= 0)
def advance(prev: list[list[int]], grid: list[list[int]], k: int, m: int, n: int) -> list[list[int]]:
"""推进一层:求两条路的行号和都恰好为 k 时,能拿到的最大好感度和。
行号和为 k 的格子 (x, k-x) 只能由上一层的 (x-1, k-x) 向下走到达,
或由 (x, k-x-1) 向右走到达,所以两条路各自只需尝试两种来向,共 4 种组合。
"""
cur = [[NEG] * (m + 1) for _ in range(m + 1)]
lo, hi = max(1, k - n), min(m, k - 1) # 第 k 层合法行号:列号 k-x 必须落在 1..n
for x1 in range(lo, hi + 1):
v1 = grid[x1 - 1][k - x1 - 1] # 路 1 所在格子的好感度
for x2 in range(lo, hi + 1):
best = max(
prev[p1][p2]
for p1 in (x1, x1 - 1) # 来向:上一层同行(向右走来)/ 上一层上一行(向下走来)
for p2 in (x2, x2 - 1)
)
if best == NEG: # 两条路都到不了的格子对,整格跳过
continue
# x1 == x2 时两路重合格子(同层列号也相同),每人只帮一次,好感度只加一次
cur[x1][x2] = best + v1 + (grid[x2 - 1][k - x2 - 1] if x1 != x2 else 0)
return cur
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
m, n = next(data), next(data)
grid = [[next(data) for _ in range(n)] for _ in range(m)]
# 按行号和 k 分层的二维 DP:f[x1][x2] 表示两条路分别走到 (x1, k-x1)、(x2, k-x2)
# 时的最大好感度和。起点 k=2:两路都在 (1,1),端点好感度为 0。
layer = [[NEG] * (m + 1) for _ in range(m + 1)]
layer[1][1] = grid[0][0]
for k in range(3, m + n + 1):
layer = advance(layer, grid, k, m, n)
print(layer[m][m])
if __name__ == "__main__":
solve()复杂度
- 时间:
——共 层,每层 个行号对、每对 4 个前驱; 时约 次基本操作。 - 空间:
——只滚动相邻两层两张表。
该复杂度与正解(C++ 同款分层 DP)同阶;本地跑 10 组真实数据单例均不足 0.05 s、峰值内存约 15 MB,在 1000 ms / 32 MB 限制内。
总结
- 回信路径反转后,问题化为两条
单调路径的并集权值最大化,重合格只计一次。 - 单调性给出天然分层
:同步推进后状态只需一对行号 ,转移只看上一层 4 个前驱, 时少加一项即完成重合处理。 - 复杂度
时间、 空间,对 绰绰有余。 - 这类「多条路径同步分层」的技巧可推广到费用流、双人取数等「多主体在网格/序列上同步推进」的问题。