整体二分总结
趁热打铁·整体二分总结
例题:
有数组 \(a[1...n]\) ,回答m个问题:\(Q(i,j,k)\):区间 \(a[i...j]\) 从小到大排序后第k个数是什么。
\(n<100000,m<5000\)
思路
没有中途的修改,可以使用离线思想。
把询问和更改(初始赋值)搞到一起,一起进行二分,具体如下:
-
doo(ll ql,ll qr,ll L,ll R):\([ql,qr]\)的询问答案在\([L,R]\)之间 -
mid=(L+R)>>1 -
从\(ql\)枚举到\(qr\):
-
若是修改操作,若
q[i].x<=mid的话,将其压到线段树里,归并在左区间。否则归并到右区间。 -
否则,查询线段树,如果
<=mid的数大于k个的话,将其归并到左区间,否则归并到右区间。 -
将左右区间归位。
-
清空线段树(只需要清空左区间,因为只有左区间才会压进去)。
-
二分下去。
code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n,m,a[1000000],ans[1000000],tr[1000000];
struct nood{
ll x,y,k,tp,wz;
};
nood q[1000000],ql[1000000],qr[1000000];
int lowbit(int x){
return x&(-x);
}
void add(ll x,ll y){
for(int i=x;i<=n;i+=lowbit(i)){
tr[i]+=y;
}
}
int getsum(ll x){
ll res=0;
for(int i=x;i>0;i-=lowbit(i)){
res+=tr[i];
}
return res;
}
void doo(ll l,ll r,ll L,ll R){
if(l>r){
return;
}
if(L==R){
for(int i=l;i<=r;i++){
if(q[i].tp==2){
ans[q[i].wz]=R;
}
}
return;
}
int mid=(L+R)>>1,t1=0,t2=0;
for(int i=l;i<=r;i++){
if(q[i].tp==1){//修改
if(q[i].x<=mid){//将其压到线段树里,归并在左区间
add(q[i].wz,1);
ql[++t1]=q[i];
}
else{//归并到右区间
qr[++t2]=q[i];
}
}
else{
int lss=getsum(q[i].y)-getsum(q[i].x-1);
if(lss>=q[i].k){//归并到左区间
ql[++t1]=q[i];
}
else{//归并到右区间
q[i].k-=lss;
qr[++t2]=q[i];
}
}
}
for(int i=1;i<=t1;i++)//清空线段树
if(ql[i].tp==1){
add(ql[i].wz,-1);
}
//将左右区间归位。
for(int i=1;i<=t1;i++){
q[l+i-1]=ql[i];
}
for(int i=1;i<=t2;i++){
q[l+t1+i-1]=qr[i];
}
//二分下去
doo(l,l+t1-1,L,mid);
doo(l+t1,r,mid+1,R);
}
int main(){
cin>>n>>m;
ll tot=0;
ll x,y,k;
for(int i=1;i<=n;i++){
cin>>x;
q[++tot].x=x,q[tot].tp=1,q[tot].wz=i;
}
for(int i=1;i<=m;i++){
cin>>x>>y>>k;
q[++tot].x=x,q[tot].tp=2,q[tot].wz=i,q[tot].y=y,q[tot].k=k;
}
doo(1,tot,-1e9,1e9);
for(int i=1;i<=m;i++){
cout<<ans[i]<<"\n";
}
}
可以自由转载

浙公网安备 33010602011771号