关于 ZJOI2018R2T2胖 ,题解&后记

题目连接

谨以纪念ZJOI2018R2,那些失败甚至退役的OIer(包括自己)。

下文认为 ∑ k \sum k k n n n同阶,故不再做区分。
首先显然被某个点松弛过的点集是段区间。
吉利sol里的做法,是直接二分这个区间的左端点和右端点,用线段树或(ST表+二分定位)来优化判定,复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
由于这里两个点A对B的影响不是简单的把带权中点拿出来,还有一个走过的边数的问题(可能A走到C了B还没到C,但B离C更近),导致并不能简单地使用数据结构上二分来优化。
一个优化办法是用压位的多叉trie来优化ST表里的二分定位,但有点无趣。
还有一个是利用吉利数据很弱,单调栈都很浅的性质,直接暴力计算单调栈里元素对当前点的影响,也可以收到不错的效果。
吉利数据过弱直接导致当场很多只写了60分(往前后扫100个)或是各种奇奇怪怪乱搞的人过了此题。
作为一个loser,我也无力评价这样的"winner"(反正自己60分都没调出来)。
赛后有人面向数据刷榜,前后只扫了24个,抢下loj rk1

但我们毕竟是新时代的青年,要崇尚科学反对迷信。
这题 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)做法看上去有点naive(反正我当场不会),其实是存在 O ( n l o g n ) O(nlogn) O(nlogn)做法的。
而且我有两个学长当场去写了,一个没调出来,另一个迫于调不出来的压力,在瓶颈上多了一个二分,常数杀成40(听说吉利数据很弱)。
最初的想法是计算每个点被松弛了几次,然后可以得到一个单次 O ( k 2 l o g n ) O(k^2logn) O(k2logn)的做法,就是在每相邻两个点之间模拟左边的点和右边的点对这段区间的松弛,这个很明显可以用单调栈来乱搞,我当时就写的是这个(虽然只指望拿个可怜的60),可惜没调出来,整场爆炸。
复杂度更优秀的做法是把每个点拆成一个向左的箭头和一个向右的箭头,用个容器(不必是priority_queue,普通queue其实也可以)来存储箭头的碰撞事件,每次碰撞后一定有一个箭头没掉,需要在线段树上二分出这个箭头能松弛到哪里,用线段树区间覆盖来模拟这个箭头对于这一排点的松弛。(这段差不多是学长出来后的原话)
实际上可以加以改进使之更好写。
我们可以不必真的模拟出每个点的被松弛,而是像官方题解一样算出每个点的控制区间,然后区间赋值可以改为单点修改,所用的线段树看上去就跟普通的 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)做法里的一样。
具体描述一下,就是从l出发的右箭头和从r出发的左箭头碰撞时,假设右箭头被冲掉(这里被冲掉的定义是距离两者的中点更远,但由于中点不一定是整点,以及可能距离相等,所以要处理的细节有点多),那么在l以左的右箭头显然对该箭头没影响,在r以右的左箭头,由于比r更靠右,所以也对该箭头无影响。
然后就只用考虑[l,r]里箭头的影响了。
对于当前试图判定的右界x,求出[x,r]区间里li+si的最大值,[l,x]区间里的li-si的最大值,和l到x的距离比较一下,即可完成判定。然后可以看出这个在线段树上二分会有些奇怪,建议先在线段树上把[l,r]这个区间的 O ( l o g n ) O(logn) O(logn)个节点定位出来,接着直接计算答案在哪个节点里,最后再上常规的树上二分。
对于l<i<r的点i对点l的影响,并不用考虑前述的“走过的边数的问题(可能A走到C了B还没到C,但B离C更近)”,因为若有一个点i<r能使l离l与i的中点的距离比i更远,那么与从l出发的右箭头碰撞的就不是从r出发的左箭头,而是从i出发的了。这样就能比官方做法少一个大的二分,以实现真正的 O ( n l o g n ) O(nlogn) O(nlogn)
好像本地50W的随机数据在O2下只要0.8s,速度海星。
可惜还是跑不过显式或隐式的借助单调栈长度的乱搞。
我的代码在此

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页