比赛中没有想到解法。在浏览其他人代码时,发现许多选手使用了相同的思路,似乎是一种广为人知的解法,特此记录一下。
思路
按照题意可知,每个坐标x的雨将会形成一个坐标为xi, 高度pi的三角形,如下图。
显然我们能发现,最高点一定是出现在雨的坐标xi上。那么首先就是如何计算每一个xi的高度。虽然一个雨的情况比较好计算,但是多个雨的情况,我们应当如何计算呢?一个比较常规的方式是进行两次前缀和的计算。下面我们以一个雨的情况进行讨论1
2
3高度: 0 0 1 2 1 0 0<br>
差分: 0 1 1 -1 -1 0<br>
二阶差分:1 0 -2 0 1
可以注意到,在二阶差分下,我们只要在左端点+1,最高点-2, 右端点+1,就能还原差分数组和高度数组。对于数据范围不大的题,这样就能完美还原图。但是这题的差分范围来到了1e9,遍历1e9来还原差分数组是不能接受的。这里的处理有两种方法。第一种是tutorial的方式,从左到右访问离散化的各个点。注意到虽然从差分数组到原数组的遍历非常麻烦,但是斜率的变化仅在二阶差分不为0的点发生。也就是说,斜率的变化是O(n)的。这样我们就能避免1e9的遍历,而对于高度的变化,我们能够通过kx+b类似的方式从第一个点开始从左向右扫描,进而算出所有点高度。在这一步上,tutorial和jiangly等oi的做法不太一样。tutorial是从左往右,一个一个点进行扫描。而jiangly的做法如下图所示
我看了很多选手都使用了这种做法。思路是对于这个三角形,延长两条边到y轴。那么截距就是pi-xi/pi+xi。然后我们将每个xi的截距相加作为每个雨对于xi点截距的贡献,参考代码中的value数组,最开始对l,j,r计算后,数组中保存的就是过xi直线的截距和。然后我们就只要考虑斜率对xi的影响了。这里只需要简单地用二阶差分计算即可。总体上来说,这种思路想要将所有雨对xi这个点的贡献,转化为一条直线表示。先计算截距贡献,再计算斜率(二阶差分转差分)。这似乎是一个非常通用的思路,思考过程和代码也非常简单。非常值得借鉴。
在解决了每个点高度的问题后,我们需要考虑,删除什么样的点,才能让大于m的点小于m。我们来看这样一张图:
假定在xi我们有pi大于m。那么显然,当且仅当雨水点(注意是顶点)xj在红色虚线上方时,该雨水的贡献是大于pi-m的(如图中的绿色点)。所以移走xj是可以让xi变成小于m的状态。
推广到多个雨水的场景,那么显然一次性能够让所有雨水小于m的点就是这种形状的交集,也就是上图中的红色区域(画板中没有类似的工具,但相信还是比较好理解的)。那么问题就是如何计算红色区域。这里tutorial和jiangly的做法又有所不同。tutorial的代码非常令人迷惑,思路如下:
通过上图,我们能够发现,决定哪一根直线在上的,是它的截距。同时斜率的组合一定是k为-1和k为1的两根直线。所以我们只要将两个斜率的直线,分别求出斜率的最大值,就能找到处于交集,或者说上方的两根直线的截距。在知道截距后,显然能够通过一些比较简单的计算获取到交点的坐标(代入方程计算,这里不展开)。这种方法非常方便,但是第一眼看到这道题时不容易得到。
jiangly使用的则是另外一种思路:对所有直线分两类:斜率为1和斜率为-1。我们现在用斜率为1的进行讨论,斜率为-1的思路非常类似。首先,我们令dp[i]为满足对所有横坐标小于i的点能够被删除需要满足点的最小高度。我们是知道每个雨的高度的,在之前差分的过程中已经计算过了。此刻我们初始化所有点对dp的贡献。对于高度小于m的点,我们直接设高度为-inf,作用是在计算中让其不产生贡献。对高度大于m的,我们设高度为pi-m,可以理解为上图中的y1为pi-m。然后我们从左往右扫描。当扫到上图中的x2时,我们能看到,原先在x1的高度pi-m此时应该是pi-m+x2-x1。这时我们将其与y2比较并取最大值。如果x2高度本来就小于m,那么-inf显然不会有贡献。从左往右计算完后,再从右往左计算k=-1的dp数组。这样两者的最大值就是在i点,高于dpi的高度即可让所有点不泛洪。这个思路非常简单明了,不需要额外的思考。是一个非常方便的思路,也非常直观。
代码
1 |
|