整体二分学习笔记

概论

整体二分一般适用于解决可以若干次二分解决的问题,当进行若干次二分的复杂度无法接受时,就可以使用整体二分。

可以使用整体二分解决的题目需要满足以下性质:
1.询问的答案具有可二分性
2.修改对判定答案的贡献互相独立,修改之间互不影响效果
3.修改如果对判定答案有贡献,则贡献为一确定的与判定标准无关的值
4.贡献满足交换律,结合律,具有可加性
5.题目允许使用离线算法
——许昊然《浅谈数据结构题几个非经典解法》

本文中出现的 \(N\)\(MAXN\) 均指值域上界。

P3834 【模板】可持久化线段树 2

以静态区间第 \(k\) 小,讲解一下算法流程。

首先将所有询问离线,然后对答案的值域 \([1,N]\) 进行分治(二分),设当前值域为 \([L,R]\),考虑对于一个区间 \([x,y]\) 查询第 \(k\) 小,若保证其答案在 \([L,R]\) 之间,但是 \([L,R]\) 之间小于 \(mid\) 的数的个数小于 \(k\),那么就说明此询问答案一定在 \([mid+1,R]\) 之间,显然这样可以根据一个 \(mid\) 值将所有询问分为两部分,将两部分的询问分别下传到 \([L,mid]\)\([mid+1,R]\) 伤,我们递归不断重复此过程,直到值域 \(L=R\),此时该区间上挂着的询问答案即为 \(L\)

如何查询\([L,R]\) 之间小于 \(mid\) 的数的个数?使用树状数组,每次将所有小于 \(mid\) 的数插入树状数组,查询前缀和做差即可。

总复杂度 \(O(Q \log^2 n)\)。整体二分操作树跑满的情况大概是这样:

pkHVMJP.png

\(a,b,c,d,e\) 代表离线下来的操作。

发现对于整个操作树,最多有 \(\log MAXN\) 层,对于每层,无论在哪个值域内我们都对每个操作进行了一次 \(\text{check}\),以本题为例,那么每一层都是 \(Q \log n\) 的,总复杂度就是 \(O(Q \log^2 n)\)

事实上,若对所有进行整体二分的询问均进行一次 \(\text{check}\) 的复杂度为 \(O(T)\),那么整体二分的复杂度为 \(O(T \log n)\)

点击查看代码
https://www.luogu.com.cn/paste/vyxkzhll

P2617 Dynamic Rankings

动态区间第 \(k\) 小。

考虑将一次修改变为在原位置上减去原来的数,再加上一个新数,离线时将原有的数也视作加一个新数,将加减操作和查询操作离线到同一个数组里,整体二分时按照操作顺序边修改树状数组b边查询,这就保证的操作的时序性。其他同静态版本。

BZOJ3110 K大数查询

区间插入一个数 \(x\),查询第 \(k\) 小。

将动态单点修改变成了区间插入,把树状数组改成线段树就好了。

P3527 [POI2011] MET-Meteors

有一个有 \(n\) 个点的线段,若干个国家,每个点归属于一个国家,有 \(k\) 次操作,每次在区间 \([l,r]\)\(x\) 个陨石,问对于每个国家 \(i\) 第几次操作后管辖的点的陨石数量能达到 \(p_i\)

对于所有国家整体二分,对于 \(mid\) 次操作,若这些操作后国家已经达到了 \(p_i\) 则分到左面,否则分到右面,考虑如何维护一个国家的陨石数量,要进行的操作是区间加单点查询,常规思路是使用树状数组维护差分,这样是 \(O(n \log n \log k)\) 的。如果不使用树状数组,直接差分修改,瓶颈在于每次都必须进行 \(O(n)\)(此时 \(n\) 固定为点的个数,不随值域改变而改变),考虑离散化,一次区间修改只对它覆盖的,答案在当前二分的区间内的国家所管辖的点有贡献,将这些点称为关键点并离散化,在 \([l,r]\) 的区间加,\(l\) 等价于原线段上在 \(l\) 右侧离 \(l\) 最近的关键点,\(r\) 等价于原线段上在 \(r\) 左侧离 \(r\) 最近的关键点,这样差分最后求前缀和的规模就是区间长度 \(len\) 级别的了,复杂度 \(O(k \log k)\)

P7424 [THUPC2017] 天天爱射击

小 C 爱上了一款名字叫做《天天爱射击》的游戏。如图所示,这个游戏有一些平行于 \(x\) 轴的木板。现在有一些子弹,按顺序沿着 \(y\) 轴方向向这些木板射去。第 \(i\) 块木板被 \(S_i\) 个子弹贯穿以后,就会碎掉消失。一个子弹可以贯穿其弹道上的全部木板,特别的,如果一个子弹触碰到木板的边缘,也视为贯穿木板。
小 C 现在知道了游戏中 \(n\) 块木板位置,以及知道了 \(m\) 个子弹射击位置。现在问你每个子弹射出去以后,有多少木板会碎掉?

考虑反向统计,对于每个木板求出在第几颗子弹处碎掉,显然这就能整体二分了,板子树状数组单点加区间查询。

以下两道题均有树剖做法,但是本文将使用更简单的做法避开树剖。

P4175 [CTSC2008] 网络管理

给出一棵树,要求支持,修改单点权值,查询路径第 \(k\) 小。

整体二分这个最小值,对于每个点统计一个 \(sum\) 值,若一个点 \(a_x \leq mid\)\(sum=1\),否则等于 \(0\),一个点 \(i\) 到根的小于等于 \(mid\) 的数的个数即为该点到根路径上所有点 \(sum\) 的加和 \(s_i\),那么查询 \((x,y)\) 之间的路径,路径上小于等于 \(mid\) 的数的个数即为 \(s_x + s_y - s_{LCA} - s_{fa[LCA]}\)。现在要维护的操作是单点加链查询,不好处理,考虑一个单点修改只会对其子树里的查询产生贡献,所以将单点加改为子树加,查询 \(s_i\) 直接单点查询 \(i\) 的值即可,这个可以利用 \(\text{dfs}\) 序维护,其原理为子树的点在 \(dfs\) 序列里是连续的,树状数组维护区间加单点查即可了。这样,对于一个询问我们只需要四次单点查,修改为一次区间加,总复杂度 \(O(q \log n \log N)\)

P3250 [HNOI2016] 网络

给出一棵树,和树上的若干条路径成为关键路径,每个关键路径有一个权值 \(val_i\),要求支持动态新增一条路径为关键路径,删除一条已有的关键路径,查询不经过一个点的关键路径的权值的最大值。

最大值可以直接二分。看到多组二分就可以考虑整体二分,二分最大值为 \(mid\) 一个查询的答案小于等于 \(mid\) 当且仅当所有 \(val\) 大于 \(mid\) 的路径全部经过该点,也就是 \(val\) 大于 \(mid\) 的路径的总数量等于经过该点的 \(val\) 大于 \(mid\) 的路径的数量,否则该查询答案大于 \(mid\)。现在要维护的操作是树上路径加单点查,继续转化,对于路径,拆成 \((x,LCA)\)\((y,LCA)\) 两条,对于每条路径进行差分,在 \(x\) 处加权,\(fa[LCA]\) 处打减标记。查询就变成了子树求和,这个原理和上一题一样,都可以用 \(\text{dfs}\) 序解决了,树状数组维护单点加区间查。

P3242 [HNOI2015] 接水果

给出一棵树,\(p\) 条路径,称为盘子,\(q\) 次查询,每次查询一条路径 \((x,y)\),求有多少个盘子是 \((x,y)\) 的子路径。

整体二分答案,问题转化为对于每个询问的路径,求 \(1\) 号盘子到 \(mid\) 号盘子中有多少个是它的子路径。

为了方便讲解,设 \(L_x\) 代表 \(x\) 子树 dfs 序最小的点, \(R_x\) 表示 \(x\) 子树 dfs 序最大的点,对于路径 \((x,y)\),保证 \(L_x < L_y\)

分类讨论一下含路径 \((x,y)\) 的所有水果 \((u,v)\) 满足的条件:

\(LCA(x,y)=x\)

此时水果一个端点在 \(y\) 的子树里。

对于另一个端点,设 \((x,y)\) 链上 \(x\) 下面的第一个点是 \(z\),那么这个端点在树上除 \(z\) 子树内的点上。

形式化的,即 \(L_u \in [1,L_z-1],L_v \in [L_y,R_y]\)\(L_u \in [L_y,R_y],L_v \in [R_z+1,n]\)

\(LCA(x,y) \neq x\)

显然 \(L_u \in [L_x,R_x],L_v \in [L_y,R_y]\)

考虑将一条链 \((x,y)\) 抽象为一个点 \((L_x,L_y)\),那么这些范围就可以转化为矩形,一个点被多少个矩形覆盖就包含了多少条路经。

然后就可以整体二分,扫描线查询了。由于是扫描线,线段都是对应的,可以不用清空树状数组以优化常数。值得注意的是,整体二分前对值域离散化同样可以有不俗的优化效果。

扩展题:P4331 [BalticOI 2004] Sequence 数字序列。思维量很大的一道题,但是主体部分与整体二分无关,这里不做讲解。

posted @ 2024-07-19 20:13  Aurora_Borealis  阅读(16)  评论(0编辑  收藏  举报