Files
python/TangDou/CSP-J/J2_2020/4.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

5.0 KiB
Raw Blame History

本题与一般的方格取数同:一般的方格取数只能 向右或向下走,在$DP$的循环中,下一次循环更新的状态不会影响先前的状态,也就是说某一行某一列更新完就已经取到最优解,这叫作:无后效性。

本题可以 向上走 破坏了规则,如果还用$f[i][j]$去描述状态,就会发生状态之间冲突依赖的情况发生,那么怎么样才能做到状态清晰呢?

答:多开一个状态!

状态表示 0:从左面过来,$1$:从上面过来,$2$:从下面过来

状态转移 从左面过来: f[i][j][0] = max({f[i][j - 1][1], f[i][j - 1][0], f[i][j - 1][2]}) + a[i][j]

从上面过来: f[i][j][1] = max(f[i - 1][j][0], f[i - 1][j][1]) + a[i][j]

从下面过来: f[i][j][2] = max(f[i + 1][j][0], f[i + 1][j][2]) + a[i][j]

初始化 由于是求 最大值,先对整个dp[N][N][3] 数组赋 long long 下的极小值: -1e18

初始化起点 (1,1) 这个点显然不能从$(2,1)$向上走一格到达,因为起点就是(1,1), 一开始就被到达了。

因此在$f[N][N][3]$数组全为极小值的情况下 单独对 f[1][1][0] = w[1][1] ,f[1][1][1] = a[1][1];

其中$w[N][N]$数组是 每个点的权值

填表顺序 Q1:起点的初始化是什么意思? 答:起点,可以是从地图的左侧边缘进入的:f[1][1][0]=a[1][1],或者是从地图的上侧边缘进入的:f[1][1][1]=a[1][1],没法从下面来,不写f[1][1][2]。

Q2:为什么要初始化第一列呢? 答:你想啊,如果按传统的办法,按行进行枚举填表,就会有问题:格子中的数据,是可以来自于它下方格子的! 也就是说,当你由上到下填充完某个格子后,当运行到下一行这个列时,

还会尝试更新刚才已经完成填充的格子数据!这在动态规划的算法中是非常忌讳的概念,被称为“有后效性”,

这样按行填表,会造成循环依赖修改,无法完成任务。

那怎么办才对呢?那是不是就没法整了呢?当然不是! 我们观察到一个事实:任何一个格子,只能从上、下、左三个方向更新数据,不能从右边过来,是吧?也就是说,

如果我们按列进行枚举,当某一列填充完数据后,再走到后面的列时,不能向左更新已填充完的数据,这样就做到了无后序性!

总结:枚举顺序不是非得按行,有时可以按列。具体是按行还是按列,是看哪种方式“无后效性”。

(3) Q3:为什么第一列要用由上到下的顺序进行初始化呢? 答:因为现在从第一列的现实情况考虑,我们也只能是由上到下去更新,从下到上没有了递推的起点初始值,没法递推。

整体递推的顺序: ① 左上下 或 ②左下上 Q:为什么一定要是左上下 或者 左下上呢?不能是 上下左,上左下等顺序吗?

答:不可以!因为你费了半天劲,整理出来的第一列,不是开玩笑!你后序的填充都要依赖第一列,也就是要先计算从左侧 转移过来, 才能利用上左侧的资源!

时间复杂度 由于需要计算每个点的三个状态 时间复杂度为 O(n\times m \times 3) 也就是 O(nm)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1010;
const int INF = 1e18;

LL f[N][N][3]; // 0:左,1:上,2:下
LL a[N][N];
int n, m;

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            cin >> a[i][j];
            f[i][j][0] = f[i][j][1] = f[i][j][2] = -INF; // 因为题目中数字有负数,不能简单初始化为0,初始化为-INF,方便求最大值
        }

    // 初始化
    f[1][1][0] = a[1][1], f[1][1][1] = a[1][1];                         // ① 起点初始化
    for (int i = 2; i <= n; i++) f[i][1][1] = f[i - 1][1][1] + a[i][1]; // ② 从上向下初始化第一列

    // 开始循环填表
    for (int j = 2; j <= m; j++) {   // 从第二列开始
        for (int i = 1; i <= n; i++) // 来源于左格子,左格子的来源随意
            f[i][j][0] = max({f[i][j - 1][1], f[i][j - 1][0], f[i][j - 1][2]}) + a[i][j];

        for (int i = 2; i <= n; i++) // 来源于上格子,上格子的来源可以是左+上,注意特判,不要出界依赖
            f[i][j][1] = max(f[i - 1][j][0], f[i - 1][j][1]) + a[i][j];

        for (int i = n - 1; i >= 1; i--) // 来源下格子,下格子的来源可以是左+下,注意特判,不要出界依赖
            f[i][j][2] = max(f[i + 1][j][0], f[i + 1][j][2]) + a[i][j];
    }

    // 输出PK后的最大值,终点格子的最大值,只能是从上面过来,或者,从左边过来,不可能从下面过来,下面是地图外了!
    cout << max(f[n][m][0], f[n][m][1]);
    return 0;
}