CSDN周赛66期图文题解 - 路灯亮度 池塘水量

news2024/7/30 6:19:03

本期非编程题考察更多是对原书的阅读理解,可能还是因为自己理解不够,翻了半天书,还是错了两道。失之我命,不多废话。

本期编程题比较符合我的胃口,有陷阱,有技巧,窃以为是最近不少期里比较有意思的中等难度的题目了。美中不足的是两道题都没有给出数据范围,从而在判断时间复杂度及选择算法上存在一定的迷惑性。

第一题 路灯亮度

有一条长度为n米的街道,上面分布着一些亮度不等的路灯。每离开某盏路灯1米,该位置受该盏路灯影响的亮度就比该盏路灯的原始亮度减少1个单位,而某个特定位置的最终亮度则等于所有影响该位置的路灯亮度的最大值,而亮度的最小值为0。

输入描述:第一行输入两个正整数n和m分别表示街道长度和路灯数量,从第二行开始的m行每行两个正整数p和l分别表示路灯的位置和光亮程度(1<=p<=n)。再随后的q行每行都有一个正整数i,表示一个位置。

输出描述:输出q行表示所有的i对应的位置的亮度值

输入样例:

10 2
1 5
4 3
1
3
10

输出样例:

5
3
0

解析

题意不难理解,就是如下图这样,一条横坐标(街道)上在某些坐标点有 “路灯”,使得左右的坐标点都被不同程度地 “照亮”。因为某些坐标点可能同时被多个 “路灯” 照到(比如下图中 a 点和 b 点),所以问题就是给出 “路灯” 的坐标以及 q 个坐标点,让你找出这 q 个坐标点能被照到的最大的亮度。

一般类似这种题目,都有两种解答方式,如果查询次数较少,我们没有必要知道 “街道” 上每个点的亮度,不需要准备查询数组,只需要查询的时候,再去找出答案好了;反之,如果查询次数很多,则会把算法的大部分时间用在实现查询数组上,这样后面的查询将以 O(1) 的方式(每次查询都是常数级)完成。

本题数据有点水(所以第一种解法得以实现),但又不是那么水(见解法二)。

解法一

题目没有给出 q 的数据范围,在用例中也没有给出 q 的大小,所以我们无法判断 q 到底有多大。在实际测试中,可以发现 q 的范围并不大。所以我们可以不用准备查询数组,而是当需要查询某坐标位置的时候,再去穷举 p 个“路灯”,查看在每个 “路灯” 的 “照射” 下,要查询的坐标点的亮度是多少,再找出其中的最大值即可。

而每个 “路灯” 对某个坐标点的 “照射亮度”,根据题意(随着距离向两侧递减),存在很简单的数学公式:路灯亮度 - 路灯与坐标点的距离,而且这个值最小为 0。

所以使用 python 一句代码就可计算得到答案。

print(max(0, max(l - abs(p - q) for p, l in lights)))

这种方法其实也是穷举,可以清楚地看到,每次查询的时间复杂度为 O(p),p 为 “路灯” 的数量。而如果有 q 次查询,整体复杂度为 O(p*q) 。

解法二 

如上所述,当查询次数不多(q 不大)时,上面的方法完全没问题。但是如果 p 和 q 都非常大,上面的方法就存在超时的风险。所以,当查询次数很多时,通常我们会先创建查询数组。以题目为例,由一条有 n 个坐标点的 “街道”,我们可以创建一个长度为 n 的数组。然后根据每个 “路灯” 的亮度,找出每个坐标点的最大 “亮度”。这样当后面查询的时候,只要从这个数组里取出相应的答案即可。

这种方法的难点在于查询数组的建立。如何降低这个过程的复杂度,是这种方法能否成功的关键。理论上通过暴力方法也可以实现,即每当发现一个 “路灯”,就把所有的坐标点的 “亮度” 都更新一遍,时间复杂度为 O(n*p) 。本题数据倒还没有水到这种程度,不出意外的超时。

实际上,大多数类似的题目,尤其是线性数组,都存在 O(n) 复杂度的方法创建查询数组,本题也不例外。通常的办法是使用动态规划,假设 “街道” 为 dp 数组,则可得转移方程如下:

从左往右:dp[i] = max(dp[i], max(dp[i-1]-1, light[i]))

从右往左:dp[j] = max(dp[j], max(dp[j+1]-1, light[j]))

由于 “路灯” 的 “亮度” 是向两边递减,所以需要从两边两个方向各遍历一次——当然也可以同时进行,只要注意好边界问题。(由于 Python 中的 max 函数操作长列表时依然比较耗时,Python 选手直接套用依然可能超时,改成 if 语句手动判断即可)

这样,只需要 O(n) 的时间复杂度,就可以更新完成查询数组。如果后面有 q 次查询的话,整体复杂度将是 O(n+q) 。

综上所述,两种方法的优劣取决于 q 值的大小。鉴于本题并未给出 q 的范围,经实测,两种方法皆可。(从测试数据来看,貌似用例中的 p 和 q 的值都比较小)

本题不难,加上已经详细解释思路及解题步骤,代码故此省略。

第二题 池塘水量

某地有n个池塘,编号为i的池塘最大容积为i(从1开始编号),一开始都没有水。每天的天气状况决定了所有水池同时的水量变化,如果某天下了容积为v的雨表示所有池塘都会增加容积v的水,当然池塘满了的话水就会溢出而流走,不会影响到次日以后。如果某天天气炎热而蒸发了容积为v的水表示所有池塘都会减少容积v的水,当然池塘最多把所有水蒸发干而变成容积为0,也不会影响到次日以后。

输入描述:第一行输入两个正整数n和m分别表示池塘数量n和天气的天数m,接下来的m行每行一个数表示每天天气导致的水容积的变化,正数表示增加、负数表示减少,0表示没有变化。

输出描述:m行,每行表示当天池塘的总水容积。

输入样例:

5 3
1
3
-2

输出样例:

5
14
5

解析

看懂题目之后,我想大部分选手想到的都是暴力模拟的解法吧?甚至会觉得此题怎会如此简单?而当进行完暴力模拟的尝试后,就会发现原来测试数据里暗藏玄机 —— 总有那么几个点 T(timeout)了。

实际上,本题正确的解法是单调栈。这么说可能并不准确,因为单调栈实际上只是一种数据结构,真正能够优化算法的是借助单调栈实现的数形结合

暴力模拟的方法就不多说了,相信大家也已经都尝试过了吧,时间复杂度为 O(n*m)。(使用 Python 能够通过的用例相较编译型语言会更少,因为 Python 的数组 / 列表在实现灵活度的同时牺牲了性能,当处理大数组 / 长列表时,如果不使用 Numpy 等第三方模块,速度要比 C 慢上十倍都不止。)

暴力模拟只能通过部分用例,让我们来看看单调栈+数形结合该怎么做。

根据题意,我们可以在纸上画出类似下面的 “池塘” 模拟图。

但是不知为什么(可能是直觉),我在读完题后,画出的模拟图是下面这个样子:所有 “池塘” 的底部都在同一水平。其实不难证明,这两张图在本题的各种情况下都等价。由于我后面的分析都是基于此图,大家可以先跟着我的思路,待充分理解后再带入上面的图形里。(也许是先入为主,窃以为这样的图更有利于分析)

观察此图,它的底(“池塘” 总个数)与高(最深的 “池塘” 深度)相等,所以在几何数学里可以将其看做一个等腰直角三角形。

先考虑最基本的情形,当部分 “池塘” 因 “落雨” 而注水时,该图形变成下面的样子:

要计算所有 “池塘” 积水的容积,相当于计算图中蓝色部分的面积 S。而这部分面积可以像下面这样拆分成两部分,从而得到 S=S_{0}+S_{1} :

根据题意可知,h_{1} 等于降水 v。很显然,S_{0} 也是一个等腰直角三角形,所以 w_{1}=n-h_{1} 。

S_{1} 是平行四边形,所以它的面积 S_{1}=h_{1}*w_{1} 。而 S_{0} 的面积计算稍微有点特殊,这里不能使用等腰直角三角形的计算公式 h_{1}*h_{1}/2 。因为实际上,从微积分的角度来看,等腰直角三角形可以看成是 h_{1} 个从 0 到 h_{1} 排列的一组数字的集合,从而它的面积计算公式等价于高斯求和的形式 h_{1}*(0+h_{1})/2 。但在本题里,因为这个 “三角形” 实际上是从 1 到 h_{1} 的排列,所以它的面积应该是 h_{1}*(1+h_{1})/2 。

从而得到总面积计算公式 S = h_{1}*(1+h_{1})/2 + h_{1}*w_{1},也就是所有 “池塘积水” 的容积。

于是,就单次 “降雨” 来看,我们已经通过数形结合的方法,将暴力模拟的 O(n) 线性复杂度的算法优化成了 O(1) 的常数阶算法。接下来,我们来分析一下更复杂的情形。

第二天,假设 “池塘” 的水被 “蒸发” 时,所有 “池塘” 水位同时下降,我们将会得到下面的图形(左边的 “池塘” 干涸了):

这时我们注意到,蓝色部分的面积仍然等于 h_{1}*(1+h_{1})/2 + h_{1}*w_{1},只不过 h_{1} 的数值发生了变化,而 w_{1} 却并没有改变——这就是我们为什么要在上一步里多增加一个变量,而不是直接使用 n-h_{1} 的原因。所以,我们只要将原先的 h_{1} 减去这一天 “蒸发”的水量 v,得到新的 h_{1},依然能通过上述公式一步计算出当前所有 “池塘积水” 的容积。

假设第三天又发生了 “降雨”,却又回不到最初的水量,这时产生的图形长这个样子:

重点来了,这时产生了一个新的平行四边形,它的面积 S_{2} = h_{2}*w_{2} 。很显然 h_{2} 等于这天的降水量 v ,但 w_{2} 等于多少呢?我们放大来看:

w_{2} 应该等于前一天平面没有水的长度减去 h_{2} ,而回到前一天的图形,可以看到,这个长度实际上等于前一天 “蒸发” 的水量 v 。

并且这个高度为 v 的小等腰直角三角形,与大的高度为 n 的等腰直角三角形(由所有 “池塘” 组成)是等比关系。

我们暂停一下,从头捋一下至今为止用到的变量,用 v_{1},v_{2},v_{3} 表示三天的 “降雨” / “蒸发” 的体积:

  • 最开始第 0 天,所有 “池塘” 为空,“三角形” 的底和高都是 n
  • 第一天 “降雨”,h_{1}=v_{1}w_{1}=n-h_{1}
  • 第二天 “蒸发”,h_{1}=h_{1}-v_{2}w_{1} 不变
  • 第三天 “降雨”,h_{1}=h_{1}+v_{3}w_{1} 不变,h_{2}=v_{3}w_{2}=v_{2}-h_{2}

由于第二天和最开始第 0 天的情形是等比关系,所以我们要在第 0 天记录 n,第二天记录 v_{2},这样在第三天的时候就可以通过公式一步计算出总面积 S = S_{0} + \sum_{i=1}^{k}S_{i} ,其中 k 等于左边平行四边形的个数。

如果后面的 “降雨” 如此反复,将会形成下面的图形:

重点,更复杂的情况来了。很显然 “降雨” / “蒸发” 不可能这么规律,当 “降雨” 超过最左边的平行四边形的边长 w_{i} 时,它将会被 “淹没”,而当 “蒸发” 的容积大于最左边平行四边形的高度 h_{i} 时,它将会 “消失”:

轮到我们的单调栈出场了。由于左边的平行四边形的高度是单调递减的,在 “降雨” 和 “蒸发” 的过程中最左边的平行四边形还会 “增加” 或 “减少”,对于这种情况,我们可以维护一个单调栈。很显然,栈里要维护的变量有两个,h_{i} 和 w_{i},可以用列表表示 [h_{i},w_{i}] 。还记得我们前面提到过要在第 0 天和第二天记录 n 和 v_{2} 吗?这就相当于完全无水和部分水面 “干涸” 的时候,栈顶有一个高度为 0 的元素 [0,n] 或 [0,v_{2}],因为高度为 0,它的存在并不会影响计算结果,反而使得栈的维护更加容易。

本题数形结合的分析过程到这里就结束了,相信聪明的你一点就通。

综合上述所有分析,总结栈的维护过程,并用代码实现:

  • “降雨” 时,
    • 最左边(栈顶)的平行四边形的 w_{i} 会发生变化,当 “降雨” 的容积 v 大于栈顶的 w_{i} 时,栈顶元素 [h_{i},w_{i}] 出栈,然后循环检查新栈顶的 w_{i} ;
    • 在此过程中,依次将弹出的栈顶的 w_{i} 减去 v 以抵消变化,得到最后的水位以更新栈顶的 w_{i} ;
    • 最后一步,再把栈里所有元素的 h_{i} 都加上此次 “降雨” 的容积 v 。完成更新。
    • 如果在第一步里栈内所有元素都弹出,说明所有 “池塘” 都满了,入栈 [n, 0]
  • “蒸发” 时,
    • 最左边(栈顶)的平行四边形的 h_{i} 会发生变化,当 “蒸发” 的容积 v 大于栈顶的 h_{i} 时,栈顶元素 [h_{i},w_{i}] 出栈,然后循环检查新栈顶的 h_{i} ;
    • 在此过程中,依次累加弹出的栈顶的 w_{i} 以得到 “干涸” 的水面宽度,得到最后的宽度 \sum w+v_{i},入栈 [0, \sum w+ v_{i}]
    • 最后一步,再把栈里所有元素的 h_{i} 都减去此次 “蒸发” 的容积 v 。完成更新。
    • 如果在第一步里栈内所有元素都弹出,说明所有 “池塘” 都干涸了,入栈 [0,n]

而计算每一天的所有 “池塘积水” 的容积,使用公式即可得到:

S = S_{0} + \sum_{i=1}^{k}S_{i}

带入变量:

S = h_{1}*(1+h_{1})/2 + \sum_{i=1}^{k}h_{i}*w_{i}

代码(部分)
n, m = map(int, input().split())
h = [[0, n]] # 初始化栈,所有池塘都干涸的情况
for _ in range(m):
    v = int(input())
    if v >= 0: # 水位上升
        temp = v
        while h and h[-1][1] < temp:
            temp -= h.pop()[1]
        if not h: h.append([n, 0]) # 所有池塘都满了
        else:
            h[-1][1] -= temp # 更新栈顶 “水面宽度”
            for i in h: i[0] += v # 更新栈内所有 “平行四边形” 的 “高度”
    else:
        # 请参考题解试着自己补全水位下降的代码

    s = h[0][0]*(1+h[0][0])//2 + sum(i[0]*i[1] for i in h)
    print(s)

该算法的时间复杂度为 O(m*k),其中 m 是给定的天数,k 是栈内元素的数量。说实话,这个 k 我不太好判断,理论上,当每天的 “降雨” / “蒸发” 极度变态时(形成锯齿状), k=min(n, m) 。但我用本题所有用例实测发现,k 的值不超过 7,相当于常数了。或可说明平均情况下(或单就本题而言),复杂度为 O(m) 。

本题或许还存在别的解法,不过相信思路大同小异,画图出来,一切就了然了。


码字不易,画图更不易,如果觉得上述题解对你有所帮助,希望留下你的点赞评论与鼓励。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/803070.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为eNSP通过VMnet8虚拟网卡,NAT转换访问互联网

防火墙上配置&#xff1a; 配置G1/0/1接口IP地址&#xff0c;其实G1/0/1的IP就是终端PC1的网关地址。 配置G1/0/0接口自动获取IP地址&#xff0c;从VMnet8中自动获取地址。 配置安全区域zone,把对应的接口加入到对应的zone中 配置安全策略&#xff0c;放通trust安全区域到u…

基于Vue3+LeaderLine实现画线时对应点放大精确选点功能

我们在画线时&#xff0c;可能开始结束位置选择不准&#xff0c;导致测量结果会有偏差&#xff0c;所以新增放大功能&#xff0c;如下所示 核心源码&#xff1a; <template>...<el-col :span"2" v-if"largeImageSelected"><!-- 显示在右…

Redis简介,设置redis内存大小,设置redis淘汰机制,查看内存占用情况,内存占用分析

为什么使用Redis缓存数据库 我们日常的开发&#xff0c;无非是对数据的处理。程序的定义也可以这样狭义的解释&#xff1a;算法数据。可见数据库是多么重要的工具。但是关系型数据库的读写能力在200-1000次/秒不等&#xff0c;服务器好点可能更多&#xff0c;这导致在高并发的…

w10修改cmd用户中文名

注销当前使用的账户&#xff0c;使用Administrator登录。右击左下角的开始&#xff0c;找到注销 一般注销之后&#xff0c;是没有Administrator登录的&#xff0c;因为Windows自动将它禁用了&#xff0c;所以在注销之前&#xff0c;你应该先右击我的电脑&#xff0c;点击管理&a…

学习笔记十三:云服务器通过Kubeadm安装k8s1.25,供后续试验用

Kubeadm安装k8s1.25 k8s环境规划&#xff1a;初始化安装k8s集群的实验环境先建生产环境服务器&#xff0c;后面可以通过生成镜像克隆node环境修改主机名配置yum源关闭防火墙关闭selinux配置时间同步配置主机 hosts 文件&#xff0c;相互之间通过主机名互相访问 **192.168.40.18…

argo workflows archived 归档设置

由于工作需要配置argo workflows归档&#xff0c;介绍一下大致步骤: 文章目录 1.在k8s中是找这个configmap2.编辑configmap3 配置数据库用户名和密码&#xff1a;4.把workflow这个pod删掉&#xff0c;让他重新生成一个 1.在k8s中是找这个configmap kubectl get cm -n argo2.编…

SpringBoot集成Lock4j 底层使用Redission 实现分布锁

Lock4j 在分布式系统中&#xff0c;实现锁的功能对于保证数据一致性和避免并发冲突是非常重要的。Lock4j是一个简单易用的分布式锁框架&#xff0c;而Redisson是一个功能强大的分布式解决方案&#xff0c;可以与Lock4j进行集成。 操作步骤 第一步&#xff1a;添加依赖 首先&…

uniAPP 视频图片预览组件

效果图 思路&#xff1a;处理文件列表&#xff0c;根据文件类型归类 已兼容 H5 ios 设备&#xff0c;测试已通过 浙政钉&#xff0c;微信小程序 视频资源因为&#xff0c;没有预览图&#xff0c;用灰色图层加播放按钮代替 <template><!--视频图片预览组件 -->&l…

【2023最新教程】6个步骤从0到1开发自动化测试框架(0基础也能看懂)

一、序言 随着项目版本的快速迭代、APP测试有以下几个特点&#xff1a; 首先&#xff0c;功能点多且细&#xff0c;测试工作量大&#xff0c;容易遗漏&#xff1b;其次&#xff0c;代码模块常改动&#xff0c;回归测试很频繁&#xff0c;测试重复低效&#xff1b;最后&#x…

使用MyBatis(1)

目录 一、什么是MyBatis 二、搭建MyBatis开发环境 &#x1f345;添加MyBatis依赖 &#x1f345;在数据库添加数据 &#x1f345;设置MyBatis配置 &#x1f388;数据库的相关连接信息&#x1f388;xml的保存和设置路径 三、使用MyBatis模式和语法操作数据库 &#x1f34…

BGP的介绍

目录 BGP基础 BGP的发展历史 BGP在企业中的应用 距离矢量型协议和路径矢量型协议的区别 BGP的特征 1.可控性 2.可靠性 3.AS-BY-AS BGP的对等关系 BGP的数据包 1.open报文 2.Keepalive报文 3.Update报文 4.Notification 报文 5.Route-refresh报文 BGP的状态机 …

GitLab备份升级

数据备份(默认的备份目录在/var/opt/gitlab/backups/下&#xff0c;生成一个以时间节点命名的tar包。) gitlab-rake gitlab:backup:create新建repo源&#xff0c;升级新版本的gitlab vim /etc/yum.repos.d/gitlab-ce.repo [gitlab-ce] namegitlab-ce baseurlhttps://mirrors.…

EMQ X 集群部署

EMQ X 消息服务器集群基于 Erlang/OTP 分布式设计,集群原理可简述为下述两条规则: MQTT 客户端订阅主题时,所在节点订阅成功后广播通知其他节点:某个主题(Topic)被本节点订阅。 MQTT 客户端发布消息时,所在节点会根据消息主题(Topic),检索订阅并路由消息到相关节点。 1. 集…

ICLR 2023 | 用于分布外泛化的拓扑感知鲁棒优化

论文链接&#xff1a;https://openreview.net/pdf?idylMq8MBnAp 代码链接&#xff1a;GitHub - joffery/TRO: The Pytorch implementation for "Topology-aware Robust Optimization for Out-of-Distribution Generalization" (ICLR 2023) 01. 研究背景 近年来&…

虹科分享 | 工业4.0:IO-Link wireless等先进通信技术是不可或缺的支柱

近年来&#xff0c;随着工业4.0&#xff08;工业革命4.0&#xff09;的提出&#xff0c;制造业转型浪潮拉开帷幕。工业4.0是将数字技术整合到传统的制造流程中&#xff0c;从而创建智能工厂。对此类工厂的最初要求是工厂各个层面的完全连接&#xff0c;从制造车间一直到管理和云…

重大更新|Sui主网即将上线流动性质押,助力资产再流通

Sui社区一直提议官方上线流动质押功能&#xff0c;现在通过SIP过程&#xff0c;已经升级该协议以实现这一功能。 Sui使用委托权益证明机制&#xff08;DPoS&#xff09;来选择和奖励负责运营网络的验证节点。为了保障网络安全&#xff0c;验证节点通过质押SUI token获得质押奖…

win10小任务栏显示日期

win10存在一个问题, 就是任务栏为小任务栏时, 无法显示系统日期, 只能显示时间, 对这个问题有两个办法, 1, 把小任务栏改为大任务栏, 日期就能显示出来 但如果你就是想用小任务栏, 不想用大任务栏, 又想显示日期, 只靠win10内置的设置是没法实现的, 只能借助其他软件, 具体看…

HTTP协议揭秘:探寻互联网的背后密码、探秘数据传输的奥秘

HTTP&#xff08;超文本传输协议&#xff1a;Hypertext Transfer Protocol&#xff09;是一种用于在Web上传输数据的协议&#xff0c;它是互联网上最重要的应用层协议之一。从诞生至今&#xff0c;HTTP一直扮演着连接世界的通信桥梁的角色&#xff0c;在互联网的发展和普及中发…

数组练习题,数组的动态初始化

数组的遍历 定义一个数组&#xff0c;求和 int[] arr {1,2,3,4,5,6,7};int sum 0;for (int i 0; i <arr.length ; i) {sum sum arr[i];}System.out.println(sum);定义一个数组&#xff0c;统计数组里面一共有多少能够被3 整除的数字&#xff1a; int[] arr1 {4,62,8…

JavaScript快速入门:ComPDFKit PDF SDK 快速构建 Web端 PDF阅读器

JavaScript快速入门&#xff1a;ComPDFKit PDF SDK 快速构建 Web端 PDF阅读器 在当今丰富的网络环境中&#xff0c;处理 PDF 文档已成为企业和开发人员的必需品。ComPDFKit 是一款支持 Web 平台并且功能强大的 PDF SDK&#xff0c;开发人员可以利用它创建 PDF 查看器和编辑器&…