【学习笔记】整体二分
一. 整体二分概念
整体二分的主体思路就是把多个查询一起解决,是一个离线算法。
其要求:
-
询问的答案具有可二分性
-
修改对判定答案的贡献互相独立,修改之间互不影响效果
-
修改如果对判定答案有贡献,则贡献为一确定的与判定标准无关的值
-
贡献满足交换律,结合律,具有可加性
-
题目允许使用离线算法
其大体结构框架:
设 \([l,r]\) 为答案的值域,\([s,t]\) 为答案的定义域。
那么二分答案时考虑下标在 \([s,t]\) 中的操作和询问,答案在值域 \([l,r]\) 中。
二. 静态区间第 \(k\) 小
由一道题引入整体二分的具体实现。
给定 \(n\) 个整数构成的序列 \(a\),将对于指定的闭区间 \([l, r]\) 查询其区间内的第 \(k\) 小值。
\(1 \leq n,m \leq 2\times 10^5\),\(|a_i| \leq 10^9\),\(1 \leq l \leq r \leq n\),\(1 \leq k \leq r - l + 1\)。
静态区间第 \(k\) 小,一个经典的问题。
显然可以用主席树做,这里我们考虑用整体二分。
与 cdq 分治类似,将询问和修改都看成“操作”。我们首先把所有操作按时间顺序存入数组中,然后开始分治,定义函数
solve(l,r,s,t)
表示在值域 \([l,r]\) 上二分处理 \([s,t]\) 这些操作
在每一层分治中,考虑利用数据结构(常用树状数组)统计当前查询的答案和 \(mid =\dfrac{l+r}{2}\) 之间的关系。
根据查询出来的答案和 \(mid\) 间的关系(\(<=mid\) 或者 \(>mid\) )将当前处理的操作序列分为两份,并分别递归处理(注意修改和询问都要递归)。
边界:当 \(l=r\) 时,找到答案,记录答案并返回即可。
需要注意的是,在整体二分过程中,solve(l,r,s,t)
只处理答案在 \([l,r]\) 内的询问,最终答案范围不在 \([l, r]\) 的询问会在其他 solve
函数中处理。
比如,我们现在有这样一个 \(n=7\) 的序列:
\([1,5,3,6,4,7,2]\)
查询次数为 \(3:\)
\(q_1=2,5,3\to ans=4\)
\(q_2=4,4,1\to ans=6\)
\(q_3=1,6,2\to ans=3\)
考虑用整体二分的思想。
原序列的值域显然为 \([1,7]\)。
这里注意最好先将原序列离散化后求值域。
那么 \(mid=\dfrac{1+7}{2}=4\)。
考虑将这个序列分成两类:
一类是权值 \(\le 4\),另一类是 \(>4\) 的。
我们把这两类用下标表示出来:
\(\le 4:(1,3,5,7)\)
\(>4:(2,4,6)\)
注意这里指下标,而不是权值。
考虑第一个询问 \(q_1=2,5,3\)。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+6;
const int inf=1e9;
int n,m,tr[N],ans[N];
struct node{
int x,y,k,id;
bool flag;
}a[N],b[N],c[N];
inline int lowbit(int x){
return x&(-x);
}
inline void update(int x,int val){
while(x<=n){
tr[x]+=val;
x+=lowbit(x);
}
return;
}
inline int query(int x){
int res=0;
while(x){
res+=tr[x];
x-=lowbit(x);
}
return res;
}
inline void solve(int l,int r,int s,int t){
if(s>t)return;
if(l==r){
for(int i=s;i<=t;i++)if(a[i].flag)ans[a[i].id]=l;
return;
}
int mid=(l+r)>>1,p=0,q=0;
for(int i=s;i<=t;i++){
if(!a[i].flag){
if(a[i].x<=mid){
update(a[i].id,1);
b[++p]=a[i];
}
else c[++q]=a[i];
}
else{
int res=query(a[i].y)-query(a[i].x-1);
if(res>=a[i].k)b[++p]=a[i];
else{
a[i].k-=res;
c[++q]=a[i];
}
}
}
for(int i=1;i<=p;i++)if(!b[i].flag)update(b[i].id,-1);
for(int i=1;i<=p;i++)a[i+s-1]=b[i];
for(int i=1;i<=q;i++)a[i+s+p-1]=c[i];
solve(l,mid,s,s+p-1);
solve(mid+1,r,s+p,t);
return;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
a[i].x=x;
a[i].y=1;
a[i].k=inf;
a[i].id=i;
a[i].flag=false;
}
for(int i=1;i<=m;i++){
int x,y,k;
scanf("%d%d%d",&x,&y,&k);
a[i+n].x=x;
a[i+n].y=y;
a[i+n].k=k;
a[i+n].id=i;
a[i+n].flag=true;
}
solve(-inf,inf,1,n+m);
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
return 0;
}