本题与一般的方格取数同:一般的方格取数只能 **向右或向下走**,在$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)$
```cpp {.line-numbers}
#include
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;
}
```