【BIT2021程设】5. 北湖深坑——单调栈

写在前面:

本系列博客仅作为本人十一假期过于无聊的产物,对小学期的程序设计作业进行一个总结式的回顾,如果将来有BIT的学弟学妹们在百度搜思路时翻到了这一条博客,也希望它能对你产生一点帮助(当然,依经验来看,每年的题目也会有些许的不同,所以不能保证每一题都覆盖到,还请见谅)。

不过本人由于学艺不精,代码定有许多不足之处,欢迎各位一同来探讨。

同时请未来浏览这条博客的学弟学妹们注意,对于我给出完整代码的这些题,仅作帮助大家理解思路所用(当然,因为懒,所以大部分题我都只给一个伪代码)。Anyway,请勿直接复制黏贴代码,小学期的作业也是要查重的,一旦被查到代码重复会严厉扣分,最好的方法是浏览一遍代码并且掌握相关的要领后自己手打一遍,同时也要做好总结和回顾的工作,这样才能高效地提升自己的代码水平。

加油!
 


指路类似题:力扣42

成绩10开启时间2021年08月24日 星期二 09:00
折扣0.8折扣时间2021年08月30日 星期一 23:00
允许迟交关闭时间2021年10月10日 星期日 23:00

 Description

十年前,北湖还只是一个深坑,未完成蓄水工作。为了确保蓄水工作的顺利进行,我们需要对北湖的蓄水量进行粗略估计。

为了简化运算,我们假设北湖的地面是一维的,每一块宽度都为1,高度是非负整数,那么可以用一个数组来表达一块地面。

例如数组[0,1,0,2,1,0,1,3,2,1,2,1]可以用来表示下图地面:

图中绿色代表地面部分,蓝色部分代表蓄水部分,蓄水量为6。

Input

样例输入有多组。

第一行输入整数 T(1\leq T\leq 100)表示有T组用例;

接下来,对于每组用例,输入一个正整数n(1\leq n\leq 1e5)表示地面总宽度为n

接下来一行是n个数a_i(1\leq a_i\leq 1e9),用空格隔开,表示地面的高度。

Output

对于每个用例输出一行一个数字,表示蓄水总量。

测试用例 1以文本方式显示
  1. 2↵
  2. 12↵
  3. 0 1 0 2 1 0 1 3 2 1 2 1↵
  4. 5↵
  5. 5 2 3 2 4↵
以文本方式显示
  1. 6↵
  2. 5↵
1秒64M


题意分析:

        这题和上一题可以说是非常类似了,无非是上一题要“挖”,这一题要“填”(然而事实上,上一题我们也是靠的“填”)。既然要填,就想想应该是怎么个填法吧。

        首先肯定是自然想法,如果你来填你会怎么填?很自然能想到所谓的“木桶理论”——一个桶能装多少水取决于这个桶最短的板有多长,这题也是一样,一个坑能装多少水就取决于他左右两侧的“墙”更低的那个有多高,即看左右两侧的最高点更低的那个有多高,这个高度即是这个坑的水位高度,如下图所示。

        对于红色方块下方所指向的这个“坑”(用箭头的话单元格莫名其妙会被拉伸,强迫症震怒,只能这样了),左侧的最高点高度是2,右侧的最高点高度是3,于是蓄水的高度就能达到2。 

        于是我们产生了第一种自然思路,即遍历到每一个位置的时候都向左求取最高点,再向右求取最高点。但很显然,这么做是一个O(n²)的复杂度,对于1e5的数据规模,我们并不是很能接受这样的复杂度。优化的方法也很简单,就是预处理打表,在正式遍历前,先向右遍历以获取每个位置的左最大高度,再向左遍历以获取每个位置的右最大高度,之后正式遍历的时候就可以直接取用了,这样的话我们的时间复杂度就退化到了O(n),虽然是4n(算上数据输入的那一次遍历),但是4n毕竟也是n嘛。

        那么有没有比4n更快的呢?那我们就得换个思路,既然我们要填到两侧最高点较低的那个高度,那么为什么不像上一题那样边走边填呢?这样就能保证,直到山峰(最高点)前自己脚下的高度永远都是另一侧最高值。那么理论存在,实践开始。

        在这样的思路下,我们首先肯定要找到最高的那个点,这个能与数据的输入同步进行,这样就是一次遍历。在找到最高点之后,我们需要从左端开始向最高点填坑,如果前方有坑就把前方填至当前高度,直到到达最高点;之后再从右端向左走,重复上述步骤。可以思考两个问题:一是为什么需要从两端往中间走,而不能直接一个方向走到底呢?二是如果存在多个最高点怎么办,这种算法会失效吗?这两个问题留给读者自己思考。

        然而这种算法我们依然不是很喜欢,因为他比起之前的算法多了“填”这一个步骤,这也是要浪费时间的,所以虽然看起来只有2n,但每一次的操作却比之前更多了,在时间上并不会比之前节省太多下来,那么有没有更快的做法,甚至能够做到边输入数据边处理的呢?答案依然是肯定的。

        这里我们要用到栈的一种变型——单调栈(名词解释:栈名词解释:单调栈)。为什么这题适用单调栈呢?我们依然从蓄水的原理出发,这次我们不去找两边的最高点(即全局最优思路),而是走一步看一步,能蓄一点是一点(这句话莫名的熟悉,好像上一篇也这么写过,你会发现很多题都同时适用这两种思路)。不难发现,“坑”的构造都是先递减再递增(此后递减、递增均指代非严格递减、非严格递增),换句话说,如果一路下来的高度都是递减的,那么一旦遇到递增就要蓄水,蓄水之后就依然能满足递减的状态。这样我们就可以发现,他同时满足了“单调”和“后进先出”两个特性,那单调栈是最合适不过的了。

        对于不熟悉栈这个数据结构的同学,可以参考上边的链接,我在此就不多做展开了。如果你了解栈的特性的话,有了上面这个思路,我们就可以轻易地做到在一次遍历完成所有的操作。然而实现细节还是有很多值得扣的地方。具体的细节我留在下方的伪代码里细讲吧。(图解可以参考一下:力扣42-单调栈题解,当然这个图解与我的伪代码有一丝丝的区别,读者可以体会一下)


伪代码:

(这里只写思路三,因为我觉得思路一二太简单了应该看一遍就能懂)

读入数据组数;

按组循环:

        初始化栈用于存储下标i(可以用STL的stack数据结构,当然这里建议自己用数组实现栈,因为我们不只需要取用栈顶元素);

        /**我们这里的栈存储的是下标, 想想为什么不是a_i**/

        /**记得一定要初始化,如果是全局定义的栈需要清空**/

        初始化一个变量ans用于保存答案;

        长度为n的循环A:

                读入a_i

                /**此处记得,大规模读取数据不要用cin,应该用scanf或者自己实现一个快读,不然容易TLE**/

                如果a_i小于栈顶下标对应的元素,i入栈,直接进入下一次循环A;

                如果a_i等于栈顶下标对应的元素,什么都不做,直接进入下一次循环A;

                循环B条件(a_i大于栈顶下标对应的元素):

                        如果栈内只有一个元素,直接弹出,终止循环B;

                        /**为什么需要这样?**/

                        弹出栈顶元素tmp

                        蓄水高度h=min(a_{stack[top]},a_i);/**此处的top是弹出tmp之后,新的栈顶**/

                        ans =ans+a_{tmp}h之差 \timesitmp之差;

                i入栈;

        输出ans

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千里之码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值