整体二分学习笔记
概论
整体二分一般适用于解决可以若干次二分解决的问题,当进行若干次二分的复杂度无法接受时,就可以使用整体二分。
可以使用整体二分解决的题目需要满足以下性质:
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)\)。整体二分操作树跑满的情况大概是这样:
\(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 数字序列。思维量很大的一道题,但是主体部分与整体二分无关,这里不做讲解。
本文来自博客园,作者:Aurora_Borealis,转载请注明原文链接:https://www.cnblogs.com/Aurora-Borealis-Not-Found/p/18312296