写在前面:
本系列博客仅作为本人十一假期过于无聊的产物,对小学期的程序设计作业进行一个总结式的回顾,如果将来有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
样例输入有多组。
第一行输入整数 表示有组用例;
接下来,对于每组用例,输入一个正整数表示地面总宽度为。
接下来一行是个数,用空格隔开,表示地面的高度。
Output
对于每个用例输出一行一个数字,表示蓄水总量。
测试用例 1 | 以文本方式显示
| 以文本方式显示
| 1秒 | 64M |
题意分析:
这题和上一题可以说是非常类似了,无非是上一题要“挖”,这一题要“填”(然而事实上,上一题我们也是靠的“填”)。既然要填,就想想应该是怎么个填法吧。
首先肯定是自然想法,如果你来填你会怎么填?很自然能想到所谓的“木桶理论”——一个桶能装多少水取决于这个桶最短的板有多长,这题也是一样,一个坑能装多少水就取决于他左右两侧的“墙”更低的那个有多高,即看左右两侧的最高点更低的那个有多高,这个高度即是这个坑的水位高度,如下图所示。
对于红色方块下方所指向的这个“坑”(用箭头的话单元格莫名其妙会被拉伸,强迫症震怒,只能这样了),左侧的最高点高度是2,右侧的最高点高度是3,于是蓄水的高度就能达到2。
于是我们产生了第一种自然思路,即遍历到每一个位置的时候都向左求取最高点,再向右求取最高点。但很显然,这么做是一个O(n²)的复杂度,对于1e5的数据规模,我们并不是很能接受这样的复杂度。优化的方法也很简单,就是预处理打表,在正式遍历前,先向右遍历以获取每个位置的左最大高度,再向左遍历以获取每个位置的右最大高度,之后正式遍历的时候就可以直接取用了,这样的话我们的时间复杂度就退化到了O(n),虽然是4n(算上数据输入的那一次遍历),但是4n毕竟也是n嘛。
那么有没有比4n更快的呢?那我们就得换个思路,既然我们要填到两侧最高点较低的那个高度,那么为什么不像上一题那样边走边填呢?这样就能保证,直到山峰(最高点)前自己脚下的高度永远都是另一侧最高值。那么理论存在,实践开始。
在这样的思路下,我们首先肯定要找到最高的那个点,这个能与数据的输入同步进行,这样就是一次遍历。在找到最高点之后,我们需要从左端开始向最高点填坑,如果前方有坑就把前方填至当前高度,直到到达最高点;之后再从右端向左走,重复上述步骤。可以思考两个问题:一是为什么需要从两端往中间走,而不能直接一个方向走到底呢?二是如果存在多个最高点怎么办,这种算法会失效吗?这两个问题留给读者自己思考。
然而这种算法我们依然不是很喜欢,因为他比起之前的算法多了“填”这一个步骤,这也是要浪费时间的,所以虽然看起来只有2n,但每一次的操作却比之前更多了,在时间上并不会比之前节省太多下来,那么有没有更快的做法,甚至能够做到边输入数据边处理的呢?答案依然是肯定的。
这里我们要用到栈的一种变型——单调栈(名词解释:栈;名词解释:单调栈)。为什么这题适用单调栈呢?我们依然从蓄水的原理出发,这次我们不去找两边的最高点(即全局最优思路),而是走一步看一步,能蓄一点是一点(这句话莫名的熟悉,好像上一篇也这么写过,你会发现很多题都同时适用这两种思路)。不难发现,“坑”的构造都是先递减再递增(此后递减、递增均指代非严格递减、非严格递增),换句话说,如果一路下来的高度都是递减的,那么一旦遇到递增就要蓄水,蓄水之后就依然能满足递减的状态。这样我们就可以发现,他同时满足了“单调”和“后进先出”两个特性,那单调栈是最合适不过的了。
对于不熟悉栈这个数据结构的同学,可以参考上边的链接,我在此就不多做展开了。如果你了解栈的特性的话,有了上面这个思路,我们就可以轻易地做到在一次遍历完成所有的操作。然而实现细节还是有很多值得扣的地方。具体的细节我留在下方的伪代码里细讲吧。(图解可以参考一下:力扣42-单调栈题解,当然这个图解与我的伪代码有一丝丝的区别,读者可以体会一下)
伪代码:
(这里只写思路三,因为我觉得思路一二太简单了应该看一遍就能懂)
读入数据组数;
按组循环:
初始化栈用于存储下标(可以用STL的stack数据结构,当然这里建议自己用数组实现栈,因为我们不只需要取用栈顶元素);
/**我们这里的栈存储的是下标, 想想为什么不是**/
/**记得一定要初始化,如果是全局定义的栈需要清空**/
初始化一个变量用于保存答案;
长度为的循环A:
读入;
/**此处记得,大规模读取数据不要用cin,应该用scanf或者自己实现一个快读,不然容易TLE**/
如果小于栈顶下标对应的元素,入栈,直接进入下一次循环A;
如果等于栈顶下标对应的元素,什么都不做,直接进入下一次循环A;
循环B条件(大于栈顶下标对应的元素):
如果栈内只有一个元素,直接弹出,终止循环B;
/**为什么需要这样?**/
弹出栈顶元素;
蓄水高度;/**此处的是弹出之后,新的栈顶**/
与之差 与之差;
入栈;
输出;