|
|
|
|
本题与一般的方格取数同:一般的方格取数只能 **向右或向下走**,在$DP$的循环中,下一次循环更新的状态不会影响先前的状态,也就是说某一行某一列更新完就已经取到最优解,这叫作:<font color='red'><b>无后效性</b></font>。
|
|
|
|
|
|
|
|
|
|
本题可以 **向上走** 破坏了规则,如果还用$f[i][j]$去描述状态,就会发生状态之间冲突依赖的情况发生,那么怎么样才能做到状态清晰呢?
|
|
|
|
|
|
|
|
|
|
<font color='red' size=5><b>答:多开一个状态!</b></font>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**状态表示**
|
|
|
|
|
$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 <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;
|
|
|
|
|
}
|
|
|
|
|
```
|