整体二分算法题解
整体二分(Parallel Binary Search)是一种用于解决多组查询问题的有效算法,特别适用于每组查询的答案都可以通过二分查找来确定的情况。
算法思想
你说得对,整体二分的基本思想就是将所有查询一起处理,通过二分答案区间,同时对多个查询进行判定和分类。这种方法的优势在于可以共享计算过程,减少重复计算。
那么,二分该如何分呢?
对于数值,简单地把所有的数值按值域割开;对于查询,按照查找结果是否应在左区间,如果是,直接放在左区间;否则就放在右区间。
但对于上面放在右区间的操作,我们需要消除左区间对该查询操作的贡献,具体的可以画个图理解一下(老师的图不敢搬qwq)。
例题
1. 区间第 kkk 小
问题模型:在一个数列中查找某区间的第 kkk 小。
模板+树状数组
为了方便,这里把数组的每一个数都看成一次修改操作。
可以发现,在分割查看所属区间时,我们直接暴力查找会悲催地 TLE,于是我们就想到了可以套一个数据结构——树状数组中,单点加,区间查询即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int ans[N],n,m,cnt;
map<int,int> hash_,rh;
struct node{
int val,pos;
}a[N];
struct qurr{
int L,R,id,k,type;
}b[N];
int s;
qurr q1[N],q2[N];
int cnt1,cnt2;
int tr[N];
int tt;
bool cmp(node x,node y)
{
return x.val<y.val;
}
int qur(int p)
{
int ans=0;
while(p)
{
ans+=tr[p];
p-=p&(-p);
}
return ans;
}
void add(int p,int x)
{
while(p<=n)
{
tr[p]+=x;
p+=p&(-p);
}
}
void solve(int l,int r,int ql,int qr)
{
if(ql>qr) return ;
if(l==r)
{
for(int i=ql; i<=qr; i++)
{
if(b[i].type==2) ans[b[i].id]=rh[l];
}
return ;
}
int mid=(l+r)/2,cnt1=0,cnt2=0;
for(int i=ql; i<=qr; i++)
{
if(b[i].type==1)
{
if(b[i].L<=mid)
{
add(b[i].id,1);
q1[++cnt1]=b[i];
}
else
{
q2[++cnt2]=b[i];
}
}
else
{
int x=b[i].L;
int y=b[i].R;
int tem=qur(y)-qur(x-1);
if(b[i].k>tem) b[i].k-=tem,q2[++cnt2]=b[i];
else q1[++cnt1]=b[i];
}
}
for(int i=1; i<=cnt1; i++)
{
if(q1[i].type==1) add(q1[i].id,-1);
}
for(int i=1; i<=cnt1; i++)
{
b[i+ql-1]=q1[i];
}
for(int i=1; i<=cnt2; i++)
{
b[i+cnt1+ql-1]=q2[i];
}
solve(l,mid,ql,cnt1+ql-1);
solve(mid+1,r,cnt1+ql,qr);
}
int main(){
cin>>n>>m;
for(int i=1; i<=n; i++)
{
cin>>a[i].val;
a[i].pos=i;
}
sort(a+1,a+1+n,cmp);
for(int i=1; i<=n; i++)
{
int tem=a[i].val;
if(!hash_[tem])
{
hash_[tem]=++cnt;
rh[cnt]=tem;
}
b[++s]={hash_[tem],-1,a[i].pos,-1,1};
}
for(int i=1; i<=m; i++)
{
int x,y,k;
cin>>x>>y>>k;
b[++s]={x,y,i,k,2};
}
solve(0,cnt+1,1,s);
for(int i=1; i<=m; i++)
{
cout<<ans[i]<<"\n";
}
return 0;
}
2. 带修改的区间第k小
问题模型:修改某数值+查询局部第 kkk 小值。
大佬们可能会使用主席树,但这题我还是主要介绍整体二分的做法趴~
同样的,我们也把数组的数堪称修改操作,把修改和查询合并,用一个标识符 typetypetype 记录操作类型。
为了方便,我们可以把修改转化为加减,即 ai=ya_i=yai=y 转化为 ai−=ai,ai+=ya_i-=a_i,a_i+=yai−=ai,ai+=y。
主要的实现就是这些。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int ans[N],n,m,cnt;
map<int,int> hash_,rh;
int a[N];
struct qurr{
int L,R,id,k,dx,type;
}q[N];
qurr q1[N],q2[N];
int cnt1,cnt2;
int tr[N];
int tt;
int qur(int p)
{
int ans=0;
while(p)
{
ans+=tr[p];
p-=p&(-p);
}
return ans;
}
void add(int p,int x)
{
while(p<=n)
{
tr[p]+=x;
p+=p&(-p);
}
}
void solve(int l,int r,int ql,int qr)
{
if(ql>qr) return ;
if(l==r)
{
for(int i=ql; i<=qr; i++)
{
if(q[i].type==2) ans[q[i].id]=l;
}
return ;
}
int mid=(l+r)/2,cnt1=0,cnt2=0;
for(int i=ql; i<=qr; i++)
{
if(q[i].type==1)
{
if(q[i].R<=mid)
{
add(q[i].id,q[i].dx);
q1[++cnt1]=q[i];
}
else
{
q2[++cnt2]=q[i];
}
}
else
{
int x=q[i].L;
int y=q[i].R;
int tem=qur(y)-qur(x-1);
if(q[i].k>tem) q[i].k-=tem,q2[++cnt2]=q[i];
else q1[++cnt1]=q[i];
}
}
for(int i=1; i<=cnt1; i++)
{
if(q1[i].type==1) add(q1[i].id,-q1[i].dx);
}
for(int i=1; i<=cnt1; i++)
{
q[i+ql-1]=q1[i];
}
for(int i=1; i<=cnt2; i++)
{
q[i+cnt1+ql-1]=q2[i];
}
solve(l,mid,ql,cnt1+ql-1);
solve(mid+1,r,cnt1+ql,qr);
}
int sum;
int main(){
cin>>n>>m;
for(int i=1; i<=n; i++)
{
cin>>a[i];
q[++sum]={i,a[i],i,-1,1,1};
}
for(int i=1; i<=m; i++)
{
string op;
cin>>op;
int x,y,k;
if(op=="Q")
{
cin>>x>>y>>k;
q[++sum]={x,y,i,k,-1,2};
}
else
{
cin>>x>>y;
q[++sum]={x,a[x],x,-1,-1,1};
q[++sum]={x,y,x,-1,1,1};
a[x]=y;
}
}
for(int i=1; i<=m; i++)
{
ans[i]=-1;
}
solve(0,1e9,1,sum);
for(int i=1; i<=m; i++)
{
if(ans[i]!=-1)
{
cout<<ans[i]<<"\n";
}
}
return 0;
}
3. 整体二分与单调序列
问题模型:给你一个序列,每次可以把一个数 +1 或 -1,要求把数列变成单调不降的,并且修改后的数只能出现修改前的数。
啊啊啊懒得写了。
算了,我还是写一写趴。
这里有一个性质:在满足次数最小化的前提下,一定存在一个方案是的最后序列中的每个数都是序列修改前存在的,这个可以使用数学归纳法证明一下。
考虑整体二分:
数据删除
手动狗头
算法分析
-
时间复杂度:整体二分的时间复杂度通常为O((N+Q)logNlogC),其中N是数据规模,Q是查询次数,C是值域范围。
-
空间复杂度:主要取决于递归深度和存储查询的空间,通常为O(N+Q)。
-
优势:
- 可以批量处理多个查询
- 共享计算过程,减少重复计算
- 代码结构清晰,易于理解和实现
注意事项
- 离散化处理:对于大值域问题,通常需要先进行离散化以提高效率。
- 树状数组/线段树:在整体二分中通常需要配合使用树状数组或线段树来统计区间信息。
- 边界条件:需要特别注意二分过程中的边界条件处理。
- 内存管理:对于大规模数据,需要注意内存使用情况。
Tips:本文是作者手搓,有口误请指出qwq
461

被折叠的 条评论
为什么被折叠?



