整体二分
引入
在信息学竞赛中,有一部分题可以使用二分的办法来解决。但是当这种题目有多次询问且每次询问我们对每个查询都直接二分,可能会收获一个 TLE。这时候我们就会用到整体二分。整体二分的主体思路就是把多个查询一起解决。(所以这是一个离线算法)
可以使用整体二分解决的题目需要满足以下性质:
询问的答案具有可二分性
修改对判定答案的贡献互相独立,修改之间互不影响效果
修改如果对判定答案有贡献,则贡献为一确定的与判定标准无关的值
贡献满足交换律,结合律,具有可加性
题目允许使用离线算法
——许昊然《浅谈数据结构题几个非经典解法》
解释
记
- 我们首先把所有操作 按时间顺序 存入数组中,然后开始分治。
- 在每一层分治中,利用数据结构(常见的是树状数组)统计当前查询的答案和
之间的关系。 - 根据查询出来的答案和
间的关系(小于等于 和大于 )将当前处理的操作序列分为 和 两份,并分别递归处理。 - 当
时,找到答案,记录答案并返回即可。
需要注意的是,在整体二分过程中,若当前处理的值域为
过程
注:
- 为可读性,文中代码或未采用实际竞赛中的常见写法。
- 若觉得某段代码有难以理解之处,请先参考之前题目的解释, 因为节省篇幅解释过的内容不再赘述。
从普通二分说起:
查询第 k 小:一次二分多个询问
题 1 在一个数列中查询第
小的数。
当然可以直接排序。如果用二分法呢?可以用数据结构记录每个大小范围内有多少个数,然后用二分法猜测,利用数据结构检验。
题 2 在一个数列中多次查询第
小的数。
可以对于每个询问进行一次二分;但是,也可以把所有的询问放在一起二分。
先考虑二分的本质:假设要猜一个
回过头来,对于当前的所有询问,可以去猜测所有询问的答案都是
试试完成以下代码:
参考代码如下
区间查询第 k 小:对只询问指定区间的处理
题 3 在一个数列中多次查询区间第
小的数。
涉及到给定区间的查询,再按之前的方法进行二分就会导致 check
函数的时间复杂度爆炸。仍然考虑询问与值域中点
参考代码(关键部分)
下面提供 【模板】可持久化线段树 2(主席树) 一题使用整体二分的,偏向竞赛风格的写法。
带修区间第 k 小:整体二分的完整运用
题 4 Dynamic Rankings 给定一个数列,要支持单点修改,区间查第
小。
修改操作可以直接理解为从原数列中删去一个数再添加一个数,为方便起见,将询问和修改统称为“操作”。因后面的操作会依附于之前的操作,不能如题 3 一样将统计和处理询问分开,故可将所有操作存于一个数组,用标识区分类型,依次处理每个操作。为便于处理树状数组,修改操作可分拆为擦除操作和插入操作。
优化
- 注意到每次对于操作进行分类时,只会更改操作顺序,故可直接在原数组上操作。具体实现,在二分时将记录操作的
数组换为一个大的全局数组,二分时记录信息变为 ,即当前处理的操作是全局数组上的哪个区间。利用临时数组记录当前的分类情况,进一步递归前将临时数组信息写回原数组。 - 树状数组每次清空会导致时间复杂度爆炸,可采用每次使用树状数组时记录当前修改位置(这已由 1 中提到的临时数组实现),本次操作结束后在原位置加
的方法快速清零。 - 一开始对于数列的初始化操作可简化为插入操作。
关键部分参考代码
参考习题
参考资料
- 许昊然《浅谈数据结构题几个非经典解法》
贡献者:@Sheng-Horizon@Jimmy@2018-Danny@Ir1d@kenlig@芊枫@mgt@ranwen@zhyz18DXJ@Ravenclaw-OIer@Henry-ZHR@ouuan@雷蒻@Prurite@partychicken@abc1763613206@Trisolaris@GavinZhengOI
本页面最近更新:2/3/2023, 12:00:00 AM,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用