洛谷

前言

题目传送门

正解:动态规划

挺 duliu 一道题,难度较大 qwq。

PS:因为此篇题解前后改动较多,如果有什么错误请各位奆佬提出,本蒟蒻感激不尽 awa。

题意简述

给你一个 \(n\times m\) 大小的方格阵,可以把方格中的任意一个数改为 \(0\),每次从 \((1,1)\) 到 \((n,m)\) 的得分为路上所有数字的和。求每次改动数字后能得到的最大值的最小值。

法一:时间复杂度 \(Θ(m^2n^2)\) (TLE)

这但凡是个正常人都会想到吧……前两层循环枚举变为 \(0\) 的方格坐标,后两层按照正常的方格取数做法求最大值。

结果,被校 OJ 卡了没骗到分。

\(Code\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int m,n,t,a[2005][2005];
ll ans=LONG_LONG_MAX,dp[2005][2005];
int main(){
scanf("%d %d",&m,&n);
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
scanf("%d",&a[i][j]);
}
}
for(int x=1;x<=m;x++){
for(int y=1;y<=n;y++){
memset(dp,0,sizeof(dp));
t=a[x][y];
a[x][y]=0;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
dp[i][j]=max(dp[i][j-1],dp[i-1][j])+a[i][j];
}
}
ans=min(ans,dp[m][n]);
a[x][y]=t;
}
}
printf("%lld",ans);
return 0;
}

法二:正解,时间复杂度 \(Θ(mn)\)

PS * 2:因为 me 习惯用 \(m\) 表示行 \(n\) 表示列,所以下列题解就会这么写。

有点麻烦。

如果每个点只能遍历一次,那么必须不变值和变为 \(0\) 这两种情况需要同时考虑。

首先我们可以考虑先求出从 \((1,1)\) 点出发走到 \((i,j)\) 点和从 \((m,n)\)出发走到 \((i,j)\) 点能拿到的最大分数,分别存在 \(dp1\) 数组和 \(dp2\) 数组里。

这两个数组很好求,按照每个初学 DP 者都要打的取数板子就珂以了。至于为什么要求这两个数组,我先卖个关子,待会儿就知道了(逃。

我们知道,如果你从 \((1,1)\) 出发,在走的时候不经过某个坐标为 \((i,j)\) 的点(也就是绕过这个点),你有两种情况可以绕开它:

  1. 从左边绕。

  2. 从上边绕。

给这两种方法更严谨的定义 \((i>0)\) :

  1. 从左边绕:经过点 \((x,y-i)\)。

  2. 从上边绕:经过点 \((x-i,y)\)。

对于这两种情况,我们可以分别用两个二维数组 \(l\) (左边绕)和 \(d\) (上边绕)来存:

  • \(l_{i,j}\) 表示从左边绕过 \((i,j)\) 点能获得的最大值。

  • \(d_{i,j}\) 表示从上边绕过 \((i,j)\) 点能获得的最大值。

但是如何求这两个数组呢?

直接切入可能比较麻烦。这个时候我们可以先分析这种情况:

不管是往哪边绕,也不管前面怎么走,都紧贴着点 \((i,j)\) 过路方便分析,即,从左绕一定经过点 \((i,j-1)\),从上绕一定经过点 \((i-1,j)\)。

首先分析从左边绕的情况。

看图,蓝色区域表示从 \((1,1)\) 出发到点 \((i,j-1)\) 有可能会经过的区域,红色区域表示从点 \((i+1,j-1)\) 到 \((m,n)\) 有可能会经过的区域。至于为什么选这两个点呢,相信大家看图也能明白,因为选择这两个点可以做到经过的格子不重不漏,考虑到每种情况。

如果这么算,那么从点 \((i,j-1)\) 绕过去能拿到的最大分数就是:

\[score=dp1_{i,j-1}+dp2_{i+1,j-1}
\]

现在大家知道两个 \(dp\) 数组的意义了吧,就是用来求某个区域的最大分数的。因为如果每次循环到一个点就计算此点到终点的分数还需要两层循环会超时,基于走方格的方向是可逆的,我们只需要计算终点到每个点的最大分数就可以啦 OvO。

那我们现在只求了最贴近点 \((i,j)\) 的绕法,那我们如何求出往左绕的所有情况的最大值呢?

我们之前不是用了一个数组来存往左边绕的值吗?因为循环的顺序是从上到下,从左到右的,所以在求点 \((i,j)\) 的值时,我们已经把它左上方的所有值都求出来了。现在我们可以利用这些值,每一次,我们求当前 \(score\) 与之前最大分数的较大值,那每次都求最大值就是所有情况中的最大值。

那么最后的结果就是:

\[l_{i,j}=\max(dp1_{i,j-1}+dp2_{i+1,j-1},i_{i,j-1})
\]

至于为什么我们利用的是 \(l_{i,j-1}\),大家可以自己画图感知,这个格子就位于我们前面求的必须经过的那个格子 \((i,j-1)\),那么绕过它我们就会求必须经过点 \((i,j-2)\),这样我们就又需要考虑绕过这个格子的情况,就又必须经过点 \((i,j-3)\)……这么一层一层往左推,最后可以推到点 \((i,1)\),从而把每种情况都考虑到。

从上面绕分析方法也差不多,这里不多说画张图让读者感知一下。

(您看看这两张图多像,连大小都差不多26 KB)

最后求出 \(d_{i,j}\) 的式子为:

\[d_{i,j}=\max(dp1_{i-1,j}+dp2_{i-1,j+1},d_{i-1,j})
\]

最后求答案有些麻烦,因为题目要求的是变化后最能获得的最大分数的最小值,所以 \(\max\) 和 \(\min\) 是真的挺容易用混的,这里需要特别注意。

首先每对一个格子进行操作,最后得到的答案会是下列三种情况中的一种:

  1. 从左边绕过去得到的最大分数。

  2. 从上边绕过去得到的最大分数。

  3. 经过这个格子得到的最大分数。

前两个我们已经求解了,但其实第三种情况是灰常简单的!因为要保证经过点 \((i,j)\),所以我们只需要求 \(dp1_{i,j}+dp2{i,j}\) 就可以了。但是上面那个式子算了两次 \((i,j)\) 的值,而我们因为把它变成 \(0\) 了,就一次都不能算。所以还需要减去两个 \(a_{i,j}\)。

所以最后的答案终于可能被更新了 owo:

\[ans=\min\big(ans,\max(l_{i,j},d_{i,j},dp1_{i,j}+dp2_{i,j}-2\times a_{i,j})\big)
\]

最后再强调一遍要注意最大值和最小值别用反了啊! 别问我为什么知道(悲)。

\(Code\)

#include<bits/stdc++.h>
#define ll long long //记得要开long long哦!
using namespace std;
//温馨提示细节:因为最后答案还是求的最小值,所以 ans 需要定义极大值
ll m,n,ans=LONG_LONG_MAX,a[2005][2005],dp1[2005][2005],dp2[2005][2005],l[2005][2005],d[2005][2005];
int main(){
//输入
scanf("%lld %lld",&m,&n);
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
scanf("%lld",&a[i][j]);
}
}
//求两个 dp 数组的值
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
dp1[i][j]=max(dp1[i-1][j],dp1[i][j-1])+a[i][j];
}
}
for(int i=m;i;i--){
for(int j=n;j;j--){
dp2[i][j]=max(dp2[i+1][j],dp2[i][j+1])+a[i][j];
}
}
//核心代码开始
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
l[i][j]=max(l[i][j-1],dp1[i][j-1]+dp2[i+1][j-1]);
d[i][j]=max(d[i-1][j],dp1[i-1][j]+dp2[i-1][j+1]);
ans=min(ans,max(max(l[i][j],d[i][j]),dp1[i][j]+dp2[i][j]-2*a[i][j]));
}
}
//核心代码结束,输出答案
printf("%lld",ans);
return 0;
}

写在最后

这真的是一道很好的动态规划题,很考验思维,也有很多需要注意的细节。最后,看在本人写了那么久的份上,就请您随手点一下左下角那个小小的赞吧 qwq。

P6855「EZEC-4.5」走方格 TJ的更多相关文章

  1. LibreOJ #6007. 「网络流 24 题」方格取数 最小割 最大点权独立集 最大流

    #6007. 「网络流 24 题」方格取数 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据   题目描述 ...

  2. Libre 6007 「网络流 24 题」方格取数 / Luogu 2774 方格取数问题 (网络流,最大流)

    Libre 6007 「网络流 24 题」方格取数 / Luogu 2774 方格取数问题 (网络流,最大流) Description 在一个有 m*n 个方格的棋盘中,每个方格中有一个正整数.现要从 ...

  3. 洛谷比赛 「EZEC」 Round 4

    洛谷比赛 「EZEC」 Round 4 T1 zrmpaul Loves Array 题目描述 小 Z 有一个下标从 \(1\) 开始并且长度为 \(n\) 的序列,初始时下标为 \(i\) 位置的数 ...

  4. macOS安装「oh my zsh」

    目前常用的 Linux 系统和 OS X 系统的默认 Shell 都是 bash,但是真正强大的 Shell 是深藏不露的 zsh, 这货绝对是马车中的跑车,跑车中的飞行车,史称『终极 Shell』, ...

  5. Loj #2324. 「清华集训 2017」小 Y 和二叉树

    Loj #2324. 「清华集训 2017」小 Y 和二叉树 小Y是一个心灵手巧的OIer,她有许多二叉树模型. 小Y的二叉树模型中,每个结点都具有一个编号,小Y把她最喜欢的一个二叉树模型挂在了墙上, ...

  6. Loj #2321. 「清华集训 2017」无限之环

    Loj #2321. 「清华集训 2017」无限之环 曾经有一款流行的游戏,叫做 *Infinity Loop***,先来简单的介绍一下这个游戏: 游戏在一个 \(n \times m\) 的网格状棋 ...

  7. LOJ #6192. 「美团 CodeM 复赛」城市网络 (树上倍增)

    #6192. 「美团 CodeM 复赛」城市网络 内存限制:64 MiB 时间限制:500 ms 标准输入输出   题目描述 有一个树状的城市网络(即 nnn 个城市由 n−1n-1n−1 条道路连接 ...

  8. Libre 6010「网络流 24 题」数字梯形 (网络流,最大费用最大流)

    Libre 6010「网络流 24 题」数字梯形 (网络流,最大费用最大流) Description 给定一个由n 行数字组成的数字梯形如下图所示.梯形的第一行有m 个数字.从梯形的顶部的m 个数字开 ...

  9. Luogu 2764 最小路径覆盖问题 / Libre 6002 「网络流 24 题」最小路径覆盖 (网络流,最大流)

    Luogu 2764 最小路径覆盖问题 / Libre 6002 「网络流 24 题」最小路径覆盖 (网络流,最大流) Description 给定有向图G=(V,E).设P是G的一个简单路(顶点不相 ...

随机推荐

  1. Mongo集群搭建

    1.集群角色及架构 在搭建集群之前,需要首先了解几个概念:路由,分片.副本集.配置服务器等 mongos,数据库集群请求的入口,所有的请求都通过mongos进行协调,不需要在应用程序添加一个路由选择器 ...

  2. .Net Core 常用开发工具(IDE和运行时、Visual Studio插件、Visual Studio Code插件)

    IDE和运行时 组件名 描述 可选版本 推荐版本 Visual Studio Community 社区免费版 For Visual Studio 2017 For Visual Studio 2019 ...

  3. 29、vi和vim用法详解

    vi类似于windows中的文本文件,用于普通的文本文件 vim:专家版的文件编辑器,用于shell程序型文件,带颜色,自检查语法 一般模式快捷键 O:光标到一行的首 $:光标到一行的尾 H:光标到整 ...

  4. Vue $refs无法操作element-ui组件

    比如我要操作这个dom元素↓↓↓ <el-badge :value="1" :max="99" class="message"> ...

  5. Java Set HashSet

    import java.util.HashSet; import java.util.Set; /** Set存储特点:数据无序.不可重复 Set接口的实现类: HashSet:Set接口的主要实现类 ...

  6. CentOS6.5 mini安装到VirtualBox虚拟机中

    下载Oracle VM VirtualBox 下载下来安装 下载镜像 http://archive.kernel.org/centos-vault/6.5/isos/i386/CentOS-6.5-i ...

  7. 所有的Java虚拟机必须实现在每个类或接口被Java程序 “ 首次主动使用 ” 时才初始化他们

    原文:https://www.cnblogs.com/fanjie/p/6916784.html Java程序对类的使用方式可分为两种– 主动使用– 被动使用 被动使用以后再讲,这里说说什么是主动使用 ...

  8. buu Youngter-drive

    一.查壳,发现是upx的壳,用自解压方式,脱下壳 二.之后发现打不开了,应该是要修复,不想修复了,直接拖入ida 找到关键函数,中间发生一点小插曲,发现堆栈不平衡,然后导致F5反编译失败,百度了下是A ...

  9. C语言枚举类型

    在实际编程中,有些数据的取值往往是有限的,只能是非常少量的整数,并且最好为每个值都取一个名字,以方便在后续代码中使用,比如一个星期只有七天,一年只有十二个月,一个班每周有六门课程等.以每周七天为例,我 ...

  10. python 读取 查询 更新 删除 sql2008 类及应用

    import pymssql class MSSQL: def __init__(self,host,user,pwd,db): self.host = host self.user = user s ...