一种离线算法
整体二分
引入
对于一堆询问,如果每个单独的询问都可以二分解决的话,时间复杂度为 ,但实际上每次二分都会有一些残留信息被我们扔掉,如果我们将所有询问一起二分,就可以最大时间的减小复杂度。
讲解
经典例题:区间第k大
1 | 给定一个序列 a 和一个整数 S,有 2 种操作: |
这个题可以用一个树状数组来维护,对于 序列,我们将所有小于等于 的数的位置在树状数组中 ,表示这个位置有一个小于等于 的数。
对于 1 操作,我们可以看作删除一个数再添加一个数,先看 是否大于 S,如果不大于则让这个位置 ,再看 是否大于 ,如果小于等于就在这个位置 。
对于 2 操作,可以直接执行 ask(r) - ask(l - 1)
,即为这个范围内有多少数小于等于 。
1 | 给定 a 序列,求第 k 小的数是几 |
这个问题也可以用二分来解决。每次二分一个 ,查询值域 内有多少小于 的数,记为 。如果 ,可以在值域 中接着二分。如果 ,可以令 k -= cnt,在值域 中继续二分。
1 | 给定 a 序列,有 m 个询问,每次询问区间[l, r]中的第 k 小数 |
我们可以按照上面第二题的思路,每个询问进行一次二分,时间复杂度为 ,不能承受。
考虑每次二分值域,都会有一大部分信息被扔掉。所以我们应该对所有的询问全部进行二分。
算法流程如下:
- 值域达到边界,直接将当前的这些达到边界的询问的答案记录,返回即可。
- 二分出 mid,按照上面第一题的方法,用树状数组查找 中不大于 的数的个数,记为 。
- 再应用第二题的方法,将 的询问放到 lq 序列中, 将 的询问的 k -= cnt,再放到 rq 序列中。
- 递归二分求解 lq 和 rq 序列。
同时,为了简化代码,将序列转化为 个插入操作,具体实现可以看上面的第一题。
代码:
1 |
|
扩展:带修区间第 k 大
当然可以用树套树在线做,但是也可以用整体二分,而且运行起来更加优秀。
将每个修改操作看作 2 种操作,和上面的第一题一样,小于 的就 ,添加的数小于 就 。
时间复杂度 。
完整代码:
1 |
|
练习
P1527 [国家集训队]矩阵乘法
本题维护一个二维树状数组,然后和区间第 k 大没有一点不同。
1 | struct tree_array |
P3527 [POI2011]MET-Meteors
很明显每个询问都能二分求解,时间长的肯定有更大概率收集全。
注意 的情况,这种情况可以将数组开成两倍,统计时加上长度即可。
统计每个国家收集多少的时候可以用图论的方式统计。
1 |
|
P4602 [CTSC2018] 混合果汁
本题二分也很好想出来,唯一的难点在于怎么快速回答每个询问。
维护一个权值线段树,下标存储价格,内部存储物品的个数和总价格。
一开始先按美味值排序,维护一直到 mid 的前缀中的物品即可。
1 |
|