You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5.0 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

本题与一般的方格取数同:一般的方格取数只能 向右或向下走,在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:左12LL 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;
}