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