整体二分 区间第 K 小(大)问题 详解

整体二分

整体二分是一个求解区间第K小(大)非常优秀的算法,但是要求离线处理,对于所有询问做整体的二分答案操作。相较于主席树 ,树套树,整体二分( 应该 )更加优秀。
我用主席树与整体二分写,并没有发现在时间上整体二分快多少,我自己算时间复杂度也觉得两者差不多(也可能我写的太丑了),但是空间上整体二分当然非常占优

上问题: (对于整体二分带修改的其实和不带修改其实差不多,后面会说)
给定一个长度为n数列,进行m次询问,每次询问 l 到 r 的 第 k 小的数是多少。


首先,我们考虑怎样对一个询问进行二分答案求解,从-inf到inf不断二分答案,每一次二分后,在数列中求出所询问区间内小于等于mid的个数num,如果num>=k,说明在这num个小于等于mid的数当中包含所求答案,反之答案则在区间大于mid的数当中,这时,更新数列为原数列中小于等于mid数,或者其他大于mid的数,便于下次二分答案。

而求解区间内有多少个小于等于mid的数时,单次询问当然直接遍历,但是在整体二分时,面对许多询问,我们可以用树状数组,把数列中小于等于mid的数的位置下标标记,然后sum(r) - sum(l-1)求出此区间小于等于mid的数。为了方便大家理解,我下面对于单组询问也做树状数组的处理

整体二分即是把这种二分答案操作做出整体把握,对所有询问二分,所以一定要理解这种求解方式

下面我给出模拟过程:
首先一个长度为7数列: 6(1) 2(2) 5(3) 3(4) 1(5) 9(6) 2(7) 询问 2 到 6 的第 3 小数,括号里是位置下标
an:a1 a2 …an 数列
l :二分答案的左区间
r :二分答案的右区间
L:询问区间的左区间
R:询问区间的右区间
K: 第几小

因为数列中最小为1,最大为9,我令 l = 1 , r = 9;

首先初始状态:
an: 6(1) 2(2) 5(3) 3(4) 1(5) 9(6) 2(7)
l = 1 , r = 9 , L = 2 , R = 6 , K = 3

1:
mid = (l+r)>>1 = 5;
插入 2(2) 5(3) 3(4) 1(5) 2(7) 的位置到树状数组。
查询 num = sum® - sum(L-1) = 4;
发现 num >= k , 说明答案小于等于5;
所以 令 r = mid;
并更新数列为: 2(2) 5(3) 3(4) 1(5) 2(7)

2:
mid = (l+r)>>1 = 3;
插入 2(2) 3(4) 1(5) 2(7) 的位置到树状数组。
查询 num = sum® - sum(L-1) = 3 ;
发现 num >= k , 说明答案小于等于3;
所以 令 r = mid;
并更新数列为: 2(2) 3(4) 1(5) 2(7)

3:
mid = (l+r)>>1 = 2;
插入 2(2) 1(5) 2(7) 的位置到树状数组。
查询 num = sum® - sum(L-1) = 2 ;
发现 num < k , 说明答案大于3;
所以 令 l = mid+1;K = K - 2;
并更新数列为: 3(4)

4:
l = r = 3 , 所以答案为3;

整体二分则是在上述的基础上,把询问打包存下来,每次先遍历数列,插入小于等于mid的数的位置,再处理所有询问,如果该询问的答案小于等于mid,就对应到小于等于的mid的新数列里,反之对应另一边,这样不断整体分划,直到l==r时,此时对应的某些询问,答案就都是l。

还是上代码,理解了上面,代码多看几遍应该就OK了

区间第K小

#include<iostream>
using namespace std;
const int maxn=1e5+5 , maxm=1e4 , inf=1e9+7;
int n,m,cnt;
int ans[maxn];

struct node{
   
    int x,y,k;
    int pos,tp;
};
node z[maxn+maxm],le[maxn+maxm],ri[maxn+maxm];

int sum[maxn];

int lowbit(int x){
    return x&(-x); }

void add(int x,int d){
   
    while(x<=n){
   
        sum[x] += d; x += lowbit(x);
    }
}

int query(int x){
   
    int ans = 0;
    while(x>0){
   
        ans += sum[x];  x -= lowbit(x);
    }
    return ans;
}

void CDQ(int l,int r,int L,int R)
{
   
    if(l>r||L>R) return;
    if(l==r){
   
        for(int i=L;i<=R;i++)
            if(z[i].tp) ans[z[i].pos
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值