【算法提高课】AcWing 1027. 方格取数 长期攻克提高课-day3(3/219)

题目的描述

给定一个 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,然后取它们四个方向到该集合的最大值即可

即:

cpp 复制代码
int 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)

相关推荐
小O的算法实验室4 小时前
IEEE TASE,基于MPC的多无人机协同搜索竞争群体优化方法
算法
Tbisnic5 小时前
BGE-M3 算法详解:从模型架构到三种检索方式的数学原理
算法·自然语言处理·大模型·bert·transformer·注意力机制
Brilliantwxx5 小时前
【算法从零到千】【55-58】哈希位图+常见数学运算 接口
算法
空堂与归6 小时前
用户分群找不到规律?用K-Means聚类算法自动发现数据模式
算法·机器学习·kmeans·聚类
动词ing7 小时前
【C语言】自定义函数+指针入门
c语言·开发语言·算法
重生之后端学习7 小时前
283. 移动零[简单]✅
开发语言·数据结构·算法·leetcode·职场和发展
一只小小的芙厨8 小时前
基础数论总结
笔记·学习·算法
夜不会漫长9 小时前
C++入门(1)
开发语言·c++·算法
wen_zhufeng10 小时前
IndexTTS 2.5 技术报告
人工智能·算法·机器学习
大熊背10 小时前
树莓派相机自动白平衡详解(四)
算法·树莓派·白平衡·isppipeline