整体二分学习笔记

整体二分在国外称为 parallel binary search,是一种用于解决多个二分搜索的离线算法,其核心思想是将一个状态用于多个询问中。

适用的问题/核心思想

一般的二分套路为:二分一个“指标”,对于当前要检查的指标,应用所有符合指标的操作,每个操作会产生一些贡献,最后判断贡献是否符合条件。如果我们要处理很多个二分问题而且应用操作的时间开销很大,每一个二分问题单独计算就会很慢。但经常应用完操作后的状态可以用于多个二分问题的条件检查,这就是整体二分的核心思想。

思路

对于如何重复利用操作之后的状态,一般有两种思路:一种最常见的思路是根据当前二分的指标从小到大进行检查,这样可以在之前操作之后状态上继续应用新的操作然后再进行检查。

如果因为一些因素使得无法应用第一种思路,但操作的贡献满足可加性的话,那么我们可以考虑第二种思路:记录下当前的状态的贡献,这样下一轮二分的时候我们可以在记录下的贡献上加上新的贡献。

这样说可能有点抽象,下面我们结合一个例子来说明具体的实现是怎样的。

例题

静态数组区间第 k 小

题目链接

这个题二分的指标就是第 k 大的大小,即我们要检查:这个区间 [l,r][l, r] 的第 k 大是否至少为 xx,这可以通过判断 [l,r][l, r] 内小于等于 xx 的元素的个数来实现。我们可以用一个权值数组 bb 记录所有所有小于等于 xx 的位置,即 bi=1    aixb_i = 1 \iff a_i \leq x,那么符合指标的操作就是将权值数组中的某个位置加一。显然对于大的指标我们可以在小指标操作的基础上加入新的操作,所以我们可以应用第一种思路。

对于第一种思路,一种比较简单的写法是用两个数组 l,rl, r 记录当前每个询问的答案所处的范围,在每一轮二分中根据 x=(l+r)2x = \frac {(l + r)}{2} 从小到大遍历每个询问

我们再次也将介绍第二种思路的写法,这样有助于理解下面动态区间第 k 小的做法。假设一个询问的答案在 [l,r][l, r] 中,指标为 xx,询问的区间中有 nn 个小于等于 xx 的数,nn 即为当前所有操作的总贡献,如果 n<kn < k,说明答案在 [x+1,r][x + 1, r] 中。如果我们记录下当前的贡献,下次二分的指标为 y=x+1+r2y = \frac {x + 1 + r} 2,我们只要知道询问的区间里大小在 [x+1,y][x + 1, y] 中的元素的个数,再加上之前记录的贡献,这样就相当于知道了小于等于 yy 的元素的个数。所以说在下一轮二分的时候我们只需要影响 [x+1,y][x + 1, y] 中的元素个数的操作,nkn \ge k 的情况类似。所以我们每次二分之后要将操作分成左右两组给下一轮二分用。

可以看出第一种思路的实现往往比较好写,其实大部分整体二分的题目都是用第一种思路解决的。

动态区间第 k 小

题目链接

因为询问和修改有先后顺序,所以不能用第一种思路。其实如果你理解了上一题的第二种思路的话,修改无非就是把原来的数删掉(在辅助数组中减 1),再加上修改之后的数。

[ZJOI2013]K 大数查询

题目链接

此题同样因为有先后顺序所以也不能用第一种思路,但思路和上题类似。 [l,r][l, r] 中每个集合加入一个数就相当于在辅助数组中 [l,r][l, r] 的位置上加 1,所以我们需要一个可以区间加的数据结构,最简单的就是树状数组啦。实现细节详见代码。

Meteors

题目链接

此题的修改操作为区间加,而且修改和询问没有前后顺序,所以可以用第一种思路。

AGC002D Stamp Rally

题目链接

这题思路其实不难,修改就是在并查集里连边,贡献就是连通块的大小,用第一种思路解决。