整体二分详解

介绍

整体二分是一种将所有修改和查询放在一起二分的离线算法。通过将所有操作二分,给每个查询查找一个正确的答案。

举个例子,求区间第 k k k小值。如果没有修改操作,那么用主席树就可以解决问题。但如果有修改操作的话,主席树就要套上一个数状数组,打起来十分麻烦。所以,对于这样的动态区间第 k k k小查询,我们就要用到整体二分了。

首先,我们将原数组的 n n n个值看作 n n n次插入操作,将修改操作看作一次删除操作和一次插入操作,那么所有操作都可以转化为插入、删除、查询操作。将这些操作按时间发生顺序放入队列中。这些是预处理操作。

之后开始整体二分。定义函数 g t ( h e a d , t a i l , l , r ) gt(head,tail,l,r) gt(head,tail,l,r)表示对于队列中 h e a d head head t a i l tail tail的操作,将所有查询操作赋上答案,保证这段操作中的所有查询操作的答案都在 [ l , r ] [l,r] [l,r]中。

对于 g t gt gt函数,如果 l = = r l==r l==r,那么队列中 h e a d head head t a i l tail tail的所有查询操作答案都为 l l l。否则:

  • 用数状数组维护插入和删除操作,到查询操作时,在数状数组内求这段中有多少值,设为 t m p i tmp_i tmpi
  • 再枚举一次,如果是查询操作,若该操作原有值加上 t m p i tmp_i tmpi小于等于目标值 k k k则放在第一类,否则放在第二类;如果是插入或删除操作,判断操作的位置,若操作位置小于等于 m i d mid mid,则放在第一类,否则放在第二类
  • 第一类 g t gt gt一次,第二类 g t gt gt一次

设数列长为 n n n,操作数为 q q q,则总时间复杂度为 O ( ( n + q ) l o g 2 ( n + q ) ) O((n+q)log^2(n+q)) O((n+q)log2(n+q))

整体二分的思想与 c d q cdq cdq二分类似。因为十分巧妙,所以原理肯定很难看明白,建议根据代码来理解。我就是看了代码才理解的

主函数

int main()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        w[++tot]=(node){i,a[i],0,1,0,0};
    }
    for(int i=1,x,y,z;i<=q;i++){
        ch=getchar();
        while(ch!='C'&&ch!='Q') ch=getchar();
        if(ch=='Q'){
            scanf("%d%d%d",&x,&y,&z);
            w[++tot]=(node){x,y,z,3,++qt,0};
        }
        else{
            scanf("%d%d",&x,&y);
            w[++tot]=(node){x,a[x],0,2,0,0};
            a[x]=y;
            w[++tot]=(node){x,a[x],0,1,0,0};
        }
    }
    gt(1,tot,0,inf);
    for(int i=1;i<=qt;i++){
        printf("%d\n",ans[i]);
    }
}

其中结构体 w w w存储操作。 t p tp tp表示这是插入、删除、查询中的哪一类操作。

如果是插入或删除: x x x表示操作的节点, y y y表示要插入或删除的值

如果是查询: x , y x,y x,y表示要查询的区间 [ x , y ] [x,y] [x,y] z z z表示要查询的是第几小值, i d id id表示这是第几次查询, n o w now now表示目前有多少个数在前面

a a a值要在每次修改操作中更新

gt函数

void gt(int h,int t,int l,int r){
    if(h>t) return;
    if(l==r){
        for(int i=h;i<=t;i++)
        if(w[i].tp==3) ans[w[i].id]=l;
        return;
    }
    int mid=(l+r)/2;
    for(int i=h;i<=t;i++){
        if(w[i].tp==1&&w[i].y<=mid) add(w[i].x,1);
        else if(w[i].tp==2&&w[i].y<=mid) add(w[i].x,-1);
        else if(w[i].tp==3) v[i]=find(w[i].y)-find(w[i].x-1);
    }
    for(int i=h;i<=t;i++){
        if(w[i].tp==1&&w[i].y<=mid) add(w[i].x,-1);
        else if(w[i].tp==2&&w[i].y<=mid) add(w[i].x,1);
    }//不能用memset
    int lt=0,rt=0;
    for(int i=h;i<=t;i++){
        if(w[i].tp==3){
            if(w[i].now+v[i]>=w[i].z) w1[++lt]=w[i];
            else{
                w[i].now+=v[i];w2[++rt]=w[i];
            }
        }
        else{
            if(w[i].y<=mid) w1[++lt]=w[i];
            else w2[++rt]=w[i];
        }
    }
    for(int i=1;i<=lt;i++) w[h+i-1]=w1[i];
    for(int i=1;i<=rt;i++) w[h+lt+i-1]=w2[i];
    gt(h,h+lt-1,l,mid);gt(h+lt,t,mid+1,r);
}

注:数状数组清空不能用 m e m s e t memset memset,否则时间复杂度会大很多

例题

动态区间第 k k k

code

#include<bits/stdc++.h>
using namespace std;
int n,q,tot,qt,inf=1000000000,a[100005],v[500005],tr[500005],ans[100005];
char ch;
struct node{
    int x,y,z,tp,id,now;
}w[500005],w1[500005],w2[500005];
int lb(int i){
    return i&(-i);
}
void add(int i,int p){
    while(i<=n){
        tr[i]+=p;i+=lb(i);
    }
}
int find(int i){
    int re=0;
    while(i){
        re+=tr[i];i-=lb(i);
    }
    return re;
}
void dd(int h,int t,int l,int r){
    if(h>t) return;
    if(l==r){
        for(int i=h;i<=t;i++)
        if(w[i].tp==3) ans[w[i].id]=l;
        return;
    }
    int mid=(l+r)/2;
    for(int i=h;i<=t;i++){
        if(w[i].tp==1&&w[i].y<=mid) add(w[i].x,1);
        else if(w[i].tp==2&&w[i].y<=mid) add(w[i].x,-1);
        else if(w[i].tp==3) v[i]=find(w[i].y)-find(w[i].x-1);
    }
    for(int i=h;i<=t;i++){
        if(w[i].tp==1&&w[i].y<=mid) add(w[i].x,-1);
        else if(w[i].tp==2&&w[i].y<=mid) add(w[i].x,1);
    }
    int lt=0,rt=0;
    for(int i=h;i<=t;i++){
        if(w[i].tp==3){
            if(w[i].now+v[i]>=w[i].z) w1[++lt]=w[i];
            else{
                w[i].now+=v[i];w2[++rt]=w[i];
            }
        }
        else{
            if(w[i].y<=mid) w1[++lt]=w[i];
            else w2[++rt]=w[i];
        }
    }
    for(int i=1;i<=lt;i++) w[h+i-1]=w1[i];
    for(int i=1;i<=rt;i++) w[h+lt+i-1]=w2[i];
    dd(h,h+lt-1,l,mid);dd(h+lt,t,mid+1,r);
}
int main()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        w[++tot]=(node){i,a[i],0,1,0,0};
    }
    for(int i=1,x,y,z;i<=q;i++){
        ch=getchar();
        while(ch!='C'&&ch!='Q') ch=getchar();
        if(ch=='Q'){
            scanf("%d%d%d",&x,&y,&z);
            w[++tot]=(node){x,y,z,3,++qt,0};
        }
        else{
            scanf("%d%d",&x,&y);
            w[++tot]=(node){x,a[x],0,2,0,0};
            a[x]=y;
            w[++tot]=(node){x,a[x],0,1,0,0};
        }
    }
    dd(1,tot,0,inf);
    for(int i=1;i<=qt;i++){
        printf("%d\n",ans[i]);
    }
    return 0;
}
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值