题目的描述
给定一个 n×nn\times nn×n 的方格矩阵,矩阵内部分单元格填写正整数,剩余单元格数值为 0。
起点为矩阵左上角顶点A,终点为矩阵右下角顶点B。
行走规则:每一步仅能向右走一格或向下走一格,沿方格边线从顶点移动,途经方格可拾取方格内数值;方格内数值一旦被拾取,第二次经过该方格时数值变为 0,无法重复累加。
现要求从A到B连续走两条合法路径,求两条路径拾取数字总和的最大值。

数据范围:n≤10n≤10n≤10
题目的分析
很多初学算法的同学直观思考:先跑一次 DP 求出 A→B 单条最优路径,把路径上所有格子数值清零,再第二次跑 DP 求剩余格子最优路径,两次结果相加当作答案。
这个思路不成立,漏洞非常明显:
1. 贪心地确定第一条路径,不能保证整体最优
第一次 DP 求出的只是原矩阵中价值最大的单条路径 ,但本题要求优化的是两条路径的总收益。单条路径取得的数值最大,并不意味着以它作为第一条路径后,两条路径的总收益也最大。它可能提前取走多个高价值格子,使第二条路径能够取得的数值大幅减少;反而选择一条单独收益稍低的路径,可能为另一条路径保留更多高价值格子,从而得到更大的总收益。
因此,"先求第一条最优路径,再求第二条最优路径"本质上是一种贪心策略,而这个贪心选择不具备正确性保证。两条路径需要作为一个整体同时优化。
2. 单路径 DP 的状态不足以描述两条路径之间的影响
第二条路径能够取得哪些数值,取决于第一条路径具体经过了哪些格子。然而,普通的单路径 DP 状态通常只记录到达某个位置时能够取得的最大值,并没有记录这条路径占用了哪些格子。即使第一次 DP 得到了单条最优路径并将其清零,第二次 DP 求出的也只是这条已固定路径下的最优补充路径,无法与其他第一条路径对应的组合进行比较。
所以我们该怎么做这道题呢?
我们可以在dpi,j再加两维,即:dpi1,j1,i2,j2。
这样dp就可以定义为,从走(1,1),(1,1)分别走到(i1,j1),(i2,j2)的所有路径中取得的最大值。
其实,我们还可以继续优化,我们知道一条路径走到一个点,这个点就要清零,即:两条路径走到相同的点,这个点的值只能加一次。那么我们可以设k ,k=i1+j1=i2+j2k=i1+j1=i2+j2k=i1+j1=i2+j2,这样当两次路径走到的点横纵坐标之和相等时,此时才有可能走到相同的点,反之不一定。
然后,我们可以压缩dp的维度,即dpk,i1,i2,后续计算j1,j2时我们可以通过公式:j1=k−i1,j2=k−i2j1=k-i1,j2=k-i2j1=k−i1,j2=k−i2计算,而且每次状态计算还能保证j1,j2j1,j2j1,j2唯一,所以与原四维dp的状态定义等价。
现在,我们采用闫氏DP分析法来分析这道题
首先状态表示,从上方分析得:dpk,i1,i2
集合:从走(1,1),(1,1)分别走到(i1,j1),(i2,j2)的所有路径。
属性:Max
即:dpk,i1,i2为从走(1,1),(1,1)分别走到(i1,j1),(i2,j2)的所有路径中取得的最大值。(k=i1+j1=i2+j2k=i1+j1=i2+j2k=i1+j1=i2+j2)
然后状态计算,开始划分集合,我们以最后一个集合dpk,i1,i2来划分
它可以划分成从(i1−1,j1)(i2−1,j2),(i1,j1−1)(i2,j2−1),(i1−1,j1)(i2,j2−1),(i1,j1−1)(i2−1,j2)(i1-1,j1)(i2-1,j2),(i1,j1-1)(i2,j2-1),(i1-1,j1)(i2,j2-1),(i1,j1-1)(i2-1,j2)(i1−1,j1)(i2−1,j2),(i1,j1−1)(i2,j2−1),(i1−1,j1)(i2,j2−1),(i1,j1−1)(i2−1,j2)这四个方向到(i1,j1)(i2,j2)(i1,j1)(i2,j2)(i1,j1)(i2,j2),点(i1,j1)(i1,j1)(i1,j1)的值为w1w1w1,点(i2,j2)(i2,j2)(i2,j2)的值为w2w2w2,所以我们可以把这个集合划分为dpk-1,i1-1,i2+w1+w2,dpk-1,i1,i2-1+w1+w2,dpk-1,i1,i2+w1+w2,dpk-1,i1-1,i2-1+w1+w2 ,当i1=i2和j1=j2i1=i2和j1=j2i1=i2和j1=j2同时成立时,此时w1=w2w1=w2w1=w2,我们只能让dp加一次w1,然后取它们四个方向到该集合的最大值即可
即:
cppint t = w[i1][j1]; if (i1 != i2) t += w[i2][j2]; int &x = dp[k][i1][i2]; // 状态转移方程 x = max(x, dp[k - 1][i1 - 1][i2 - 1] + t); x = max(x, dp[k - 1][i1 - 1][i2] + t); x = max(x, dp[k - 1][i1][i2 - 1] + t); x = max(x, dp[k - 1][i1][i2] + t);
闫氏DP分析图

题目的代码
题目链接:https://www.acwing.com/problem/content/1029/
cpp
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 15;
int n;
int w[N][N];
int f[N * 2][N][N];
int main()
{
scanf("%d", &n);
int a, b, c;
while (cin >> a >> b >> c, a || b || c) w[a][b] = c;
for (int k = 2; k <= n + n; k ++ )
for (int i1 = 1; i1 <= n; i1 ++ )
for (int i2 = 1; i2 <= n; i2 ++ )
{
int j1 = k - i1, j2 = k - i2;
if (j1 >= 1 && j1 <= n && j2 >= 1 && j2 <= n)
{
int t = w[i1][j1];
if (i1 != i2) t += w[i2][j2];
int &x = f[k][i1][i2];
x = max(x, f[k - 1][i1 - 1][i2 - 1] + t);
x = max(x, f[k - 1][i1 - 1][i2] + t);
x = max(x, f[k - 1][i1][i2 - 1] + t);
x = max(x, f[k - 1][i1][i2] + t);
}
}
printf("%d\n", f[n + n][n][n]);
return 0;
}
**时间复杂度:**只需遍历dp数组,所以复杂度为O(n3)\boldsymbol{O(n^3)}O(n3)
**空间复杂度:**只需开2*n*n*n个数组,所以复杂度为O(n3)\boldsymbol{O(n^3)}O(n3)