AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

AcWing 275. 传纸条【附详细证明】    原题链接    中等

作者: 作者的头像   一只野生彩色铅笔 ,  2021-05-28 15:34:42 ,  所有人可见 ,  阅读 4563


86


37

最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划

题目描述

给定一个 $n \times m$ 的矩阵,$A$ 在矩阵左上角 $(1,1)$ 的位置,$B$ 在矩阵右下角 $(n,m)$ 的位置

$A$ 要向 $B$ 传递一次纸条,且传递的过程只能向下或向右

$B$ 也要向 $A$ 传递一次纸条,且传递的过程只能向上或向左

两次过程中,经过的格子不能重合

每个格子给定一个好感度,其实就是这个格子的价值

我们要找到一个方案,使得两次传递的路线经过的所有格子的价值最大

题目解析

这题明显不是一个裸题,我们需要一层一层的拆解分析

首先想到的是能不能往 方格取数 模型上靠

对于一个从 $(n,m)$ 出发到 $(1,1)$ 的路线,且只能向上或向右走,考虑将其方向调转,则必定对应一条从 $(1,1)$ 出发到 $(n,m)$ 的路线,且只能向下或向右走

IMG_DBF1CCEFA907-1.jpeg

这两种走法的方案都是一一对应的(即任意一条路线都可以找到其对应的反向路线),因此该方案映射合法

于是该问题就变成了寻找一条从 $(1,1)$ 出发到达 $(n,m)$,每次只能向下或向右走,先后出发两次,且两次路线不能经过重复格子的最大价值方案

这样就很靠近 方格取数 模型了

关于如何解决不能经过重复格子的问题

我们先给定一个结论: 方格取数 dp模型的最优方案可以是不经过重复格子的

证明:

情况一:最优解的两条路线是相互交叉经过的

IMG_41A02C1923F3-1.jpeg

则我们可以对交叉出来的部分进行路线交换,如下图的操作

IMG_D04B2FA5BFB0-1.jpeg

于是,我们可以发现,所有的交叉路线都会映射成一种一条路线只在下方走,一条路线只在上方走的不交叉路线

因此我们只需集中解决情况二即可

情况二:最优解的两条路线不交叉,但在某些点有重合

IMG_91EF0191824B-1.jpeg

由于方格取数,对于走到相同格子时,只会累加一次格子的价值

于是我们可以使用贪心中的微调法来进行这部分的证明

对于重合的格子,我们必然可以在两条路线中找到额外的一条或两条路线,使得新的路线不发生重合

具体参照下图:

IMG_6202C63C2DFE-1.jpeg

由于原路线是最优解,则必然 $w_A = w_B = 0$,否则最优解路线必然是经过 $A$ 或 $B$ 的

因此,我们可以通过微调其中的一条路线,使之不经过重合点 $C$,同时路线的总价值没有减少

得证:最优解路线可以是不经过重复路线的 (部分证明方法参照了第一赞的题解了)

接下来就是完全参照 AcWing 1027. 方格取数 的DP分析了

关于重合格子判断一些条件都在这篇博客里详细写了

这里我偷个懒,直接用我上篇博客的内容了 (既然是我自己写的应该不算抄袭吧!)

闫氏DP分析法

$$ \begin{cases} 状态表示f_{k,i,j} \begin{cases} 属性: 路径长度为k,第一条路线到x_1=i,第二条路线到x_2=j的所有方案 \\\ 集合: 方案中的路线经过的所有物品的总价值最大 Max \end{cases} \\\ 状态转移 f_{k,i,j} = max\{f_{k-1,i,j},f_{k-1,i-1,j},f_{k-1,i,j-1},f_{k-1,i-1,j-1}\} + w \end{cases} $$

集合划分

IMG_623B17197D17-1.jpeg

状态的初值: f[2][1][1]

目标的状态: f[n + m][n][m]

Code(三重迭代写法)

#include <iostream>

using namespace std;

const int N = 55, M = 2 * N;

int n, m;
int w[N][N];
int f[M][N][N];

int main()
{
    //input
    cin >> n >> m;
    for (int i = 1; i <= n; ++ i)
    {
        for (int j = 1; j <= m; ++ j)
        {
            cin >> w[i][j];
        }
    }
    //dp
    for (int k = 2; k <= n + m; ++ k)
    {
        for (int i = 1; i < k && i <= n; ++ i)
        {
            for (int j = 1; j < k && j <= n; ++ j)
            {
                int v = w[i][k - i];
                if (i != j) v += w[j][k - j];

                int &t = f[k][i][j];
                t = max(t, f[k - 1][i][j]);
                t = max(t, f[k - 1][i - 1][j]);
                t = max(t, f[k - 1][i][j - 1]);
                t = max(t, f[k - 1][i - 1][j - 1]);
                t += v;
            }
        }
    }

    //output
    cout << f[n + m][n][n] << endl;
    return 0;
}

Code(记忆化搜索写法)

#include <iostream>
#include <cstring>

using namespace std;

const int N = 55, M = 2 * N, INF = 0x3f3f3f3f;

int n, m;
int w[N][N];
int f[M][N][N];

int dp(int k, int i, int j)
{
    if (f[k][i][j] >= 0) return f[k][i][j];

    if (k == 2 && i == 1 && j == 1) return f[k][i][j] = w[1][1];

    if (i <= 0 || i >= k || j <= 0 || j >= k) return -INF;

    int v = w[i][k - i];
    if (i != j) v += w[j][k - j];

    int t = 0;
    t = max(t, dp(k - 1, i, j));
    t = max(t, dp(k - 1, i - 1, j));
    t = max(t, dp(k - 1, i, j - 1));
    t = max(t, dp(k - 1, i - 1, j - 1));
    return f[k][i][j] = t + v;
}
int main()
{
    //input
    cin >> n >> m;
    for (int i = 1; i <= n; ++ i)
    {
        for (int j = 1; j <= m; ++ j)
        {
            cin >> w[i][j];
        }
    }
    //initialize
    memset(f, -1, sizeof f);
    //output
    cout << dp(n + m, n, n) << endl;
    return 0;
}

18 评论


用户头像
东山   2021-09-21 15:54      1    踩      回复

为甚,最后输出f[m+n][n][n]

用户头像
一只野生彩色铅笔   2021-09-21 15:59         踩      回复

和初始定义的状态有关:

f[k][i][j] 表示:走过的格子数为 k,第一条路线横坐标为 i,第二条路线横坐标为 j

用户头像
东山   2021-09-21 16:11    回复了 一只野生彩色铅笔 的评论      1    踩      回复

我明白了,因为定义的状态是f[ i1 + j1 ][ i1 ][ i2 ] 是与横坐标有关的,横坐标最大为n所以输出f[m+n][n][n]

用户头像
一只野生彩色铅笔   2021-09-21 16:12    回复了 东山 的评论         踩      回复

是的w


用户头像
looeyWei   2021-06-01 21:22      1    踩      回复

突然发现在 方格取数 一文 , 我评论里提到的 i <= n, j <= n 是错误的,应该这篇文章里才是对的,应该是 i, j <= min(k-1, n)

用户头像
一只野生彩色铅笔   2021-06-01 21:39      2    踩      回复

其实这个边界无关痛痒,DP的核心是找到初始状态和目标状态,以及找对状态的转移

这些都对的情况下,就是边界的问题,像这种额外更新了一些不重要的状态其实是无所谓的

只要找到目标状态就好了

用户头像
一只野生彩色铅笔   2021-06-01 21:40      2    踩      回复

DP其实可以看作在一个拓扑图下找最短路,其他的点的距离无所谓,目标点的最短路更新才是关键

用户头像
looeyWei   2021-06-01 21:43    回复了 一只野生彩色铅笔 的评论         踩      回复

学到了√

用户头像
acwing_254457   2023-03-29 16:36    回复了 一只野生彩色铅笔 的评论      1    踩      回复

说的太对了


用户头像
Henter   2024-08-04 22:25         踩      回复

为什么第一种代码不能初始化成这样memset(f,-0x3f,sizeof f);?这样程序不对


用户头像
铃兰の顶点   2023-01-23 13:23         踩      回复

%%%


用户头像
Wzxc   2022-08-07 23:33         踩      回复

想问一下up写的先后两次走不久没有全局最优解了吗,按照做法应该是同时走呀

用户头像
太雪菜   2022-11-20 09:56         踩      回复

写的不就是同时走吗


用户头像
ncust202105   2022-07-08 17:15         踩      回复

妙


用户头像
睡上铺的小张   2022-06-16 20:33         踩      回复

写得真好(๑•̀ㅂ•́)و✧


用户头像
onepiece_   2022-01-13 22:03         踩      回复

orz这个和前面那题是不是都可以降成二维滚动数组?

用户头像
一只野生彩色铅笔   2022-01-13 22:05         踩      回复

是的


用户头像
万般扰我   2021-11-24 10:20         踩      回复

感谢彩色铅笔✏️大佬的记忆化搜索代码


App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息