浅谈整体二分
前言
本人在 oi-wiki 学习整体二分的时候有些坎坷,于是就有了这篇博客。如有什么问题或者没有讲到的点请及时跟我反馈。
前置知识:二分答案
正文
首先我们引入一题。
给定一个长度为
n 的序列,求[l,r] 区间内的第k 小。
思路不难想,考虑二分答案。每次二分
但如果有
那么这个算法的时间复杂度为
这就要用到今天的正题——整体二分了。
整体二分的主体思路就是把多个查询一起解决,所以整体二分是一个离线算法。
可以使用整体二分解决的题目需要满足以下性质:
1.询问的答案具有可二分性。
2.问题的每次修改操作不会影响其他修改操作的结果,每次修改都是相互独立的。
3.问题的每次修改都会对答案产生一个确定的、与判定标准无关的影响。
4.问题的每次修改操作可以满足交换律、结合律的性质,且多个修改操作对答案的贡献可以进行加和。
5.题目允许使用离线算法。
我们定义一个结构体
对于询问,
因为整体二分统计答案时有单点加,区间和的操作,用树状数组维护较为方便。我们考虑用树状数组维护小于
这样查询区间第
模板:洛谷 P3834 【模板】可持久化线段树 2
整体二分解法:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5,INF=1e9;
int n,Q,ans[N],a[N],tp,c[N];
struct qwq
{
int x,y,k,id;
bool type;
}q1[N<<1],q2[N<<1],q[N<<1];
//树状数组
inline int lowbit(int x){return x&-x;}
inline void add(int x,int y)
{
while(x<=n)
{
c[x]+=y;
x+=lowbit(x);
}
}
inline int sum(int x)
{
int ans=0;
while(x)
{
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
//主体部分,如上述
inline void solve(int l,int r,int L,int R)
{
if(l>r) return;
if(L==R)
{
for(int i=l;i<=r;i++) if(q[i].type) ans[q[i].id]=L;
return ;
}
int mid=(L+R)>>1,cnt1=0,cnt2=0;
for(int i=l;i<=r;i++)
{
if(!q[i].type)
{
if(q[i].x<=mid)
{
add(q[i].id,1);
q1[++cnt1]=q[i];
}
else q2[++cnt2]=q[i];
}
else
{
int ans=sum(q[i].y)-sum(q[i].x-1);
if(ans>=q[i].k) q1[++cnt1]=q[i];
else{
q[i].k-=ans;
q2[++cnt2]=q[i];
}
}
}
for(int i=1;i<=cnt1;i++) if(!q1[i].type) add(q1[i].id,-1);
for(int i=1;i<=cnt1;i++) q[i+l-1]=q1[i];
for(int i=1;i<=cnt2;i++) q[i+l+cnt1-1]=q2[i];
solve(l,l+cnt1-1,L,mid);
solve(l+cnt1,r,mid+1,R);
}
signed main()
{
cin>>n>>Q;
for(int i=1;i<=n;i++)
{
cin>>a[i];
q[++tp]=(qwq){a[i],1,INF,i,0};
}
for(int i=1;i<=Q;i++)
{
int l,r,k;
cin>>l>>r>>k;
q[++tp]=(qwq){l,r,k,i,1};
}
solve(1,tp,-INF,INF);
for(int i=1;i<=Q;i++) cout<<ans[i]<<endl;;
return 0;
}
练习题:
洛谷 P1527 [国家集训队] 矩阵乘法
洛谷 P3527 [POI2011] MET-Meteors