整体二分 区间第 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] = l;
        return;
    }
    int mid = (l+r)>>1;
    int cntl = 0 , cntr = 0;
    for(int i=L;i<=R;i++){
        if(z[i].tp){  // 询问
            int tem = query(z[i].y) - query(z[i].x-1);
            if(tem<z[i].k) z[i].k -= tem , ri[++cntr] = z[i];
            else le[++cntl] = z[i];
        }
        else{ // 插入
            if(z[i].x<=mid) add(z[i].pos,1) , le[++cntl] = z[i]; 
            else ri[++cntr] = z[i];  
        }
    }
    for(int i=1;i<=cntl;i++) if(!le[i].tp) add(le[i].pos,-1);  // 取消标记,使树状数组清空,方便下次操作
    
    for(int i=1;i<=cntl;i++) z[L+i-1] = le[i];  // 以mid为界限分划为两个新整体,数列与询问都分开了
    for(int i=1;i<=cntr;i++) z[L+cntl+i-1] = ri[i];
    
    CDQ(l,mid,L,L+cntl-1);
    CDQ(mid+1,r,L+cntl,R);
}

int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    cnt = 0;
    for(int i=1;i<=n;i++){  //  先放原数列,因为先插入到树状数组后才能查询
        int a;  cin>>a;
        z[++cnt] = {a,1,0,i,0};   // z[i].tp==0 表示插入操作
    }
    for(int i=1;i<=m;i++){  // 后放询问
        int a,b,c;    cin>>a>>b>>c;
        z[++cnt] = {a,b,c,i,1};  // z[i].tp==1 表示询问
    }
    CDQ(-inf,inf,1,cnt);
    for(int i=1;i<=m;i++) cout<<ans[i]<<endl;
}

修改其实就只是比不修改的多了一个删除,树状数组中取消标记就好了
我直接上个需要修改的代码,与上面的大致一样

区间第K小带修改

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e4+5 , maxm=1e4+5 , inf=1e9+7;
int n,m,va[maxn],ans[maxm];

struct node{
    int l,r,k,pos,tp;
};
node z[maxn*2],zl[maxn*2],zr[maxn*2];

int C[maxn];
void add(int x,int d){
    while(x<=n) { C[x] += d , x += (x&-x); }
}
int sum(int x){
    int s = 0;
    while(x) { s += C[x] , x -= (x&-x); }
    return s;
}

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==3) ans[z[i].pos] = l;
        return;
    }
    int mid = (l+r)>>1;
    int p = 0, q = 0;
    for(int i=L;i<=R;i++){
        if(z[i].tp==1){
            if(z[i].k<=mid) add(z[i].pos,1), zl[++p] = z[i];  //  插入 
            else zr[++q] = z[i];
        }
        else if(z[i].tp==2){
            if(z[i].k<=mid) add(z[i].pos,-1), zl[++p] = z[i];  // 删除
            else zr[++q] = z[i];
        }
        else {   // 询问
            int tem = sum(z[i].r) - sum(z[i].l-1);
            if(z[i].k<=tem) zl[++p] = z[i];
            else z[i].k -= tem , zr[++q] = z[i];
        }
    }
    for(int i=1;i<=p;i++){       // 取消标记
        if(zl[i].tp==1) add(zl[i].pos,-1);
        if(zl[i].tp==2) add(zl[i].pos,1);
    }
    for(int i=1;i<=p;i++) z[L+i-1] = zl[i];
    for(int i=1;i<=q;i++) z[L+p+i-1] = zr[i];
    CDQ(l,mid,L,L+p-1);
    CDQ(mid+1,r,L+p,R);
}

int main()
{
    ios::sync_with_stdio(false);
    int t;   cin>>t;
    while(t--){
        int ki = 0,cnt=0,a,b,c;
        cin>>n>>m;
        for(int i=1;i<=n;i++){
            cin>>va[i];
            z[++ki] = (node){ 0,0,va[i],i,1 };   // z[i].tp==1 表示插入
        }
        char d;
        for(int i=1;i<=m;i++){
            cin>>d;
            if(d=='Q'){
                cin>>a>>b>>c;
                z[++ki] = (node){ a,b,c,++cnt,3 };  // z[i].tp==3 表示询问  
            }
            else {
                cin>>a>>b;
                z[++ki] = (node){ 0,0,va[a],a,2 };  // z[i].tp==2 表示删除
                z[++ki] = (node){ 0,0,b,a,1 };   //  先删除原位置,再添加新位置
                va[a] = b;
            }
        }
        CDQ(0,inf,1,ki);
        for(int i=1;i<=cnt;i++)
            cout<<ans[i]<<endl;
    }
}

不想拿金的Acmer不是好Acmer

资源下载链接为:https://pan.quark.cn/s/22ca96b7bd39Citespace是一款备受学术界青睐的文献可视化与网络分析工具,广泛应用于学术研究和知识发现领域。它能够从海量文献数据中提取关键词、作者、机构、主题等信息,并揭示其关联模式,从而洞察学科发展动态与趋势。Citespace 6.2.4作为最新版本,新增了诸多功能并进行了优化,显著提升了用户体验和分析能力。安装过程:下载的“Citespace-6.2.4.msi”是Windows系统的安装文件,双击运行后会启动安装向导。安装时需遵循提示,依次完成接受许可协议、选择安装路径、设置启动选项等步骤。安装前请确保计算机满足Citespace的系统要求,如操作系统版本、Java环境等。Java环境需求:Citespace需要Java运行环境(JRE)支持,安装前需确认已安装兼容版本的Java。若未安装,安装程序可能会自动引导下载并安装Java。软件界面与功能:Citespace的界面简洁直观,由菜单栏、工具栏和工作区构成。用户可通过主界面导入BibTeX、EndNote、RIS等多种格式的文献数据,软件会自动解析信息,并以共词网络、引文网络等多种网络图形式展示。网络图分析功能:这是Citespace的核心优势之一。用户可自由选择可视化参数,如调整节点小、颜色、边的粗细等,以突出关键信息。时间滑块功能还能动态展示知识领域的演变过程。聚类与主题挖掘:Citespace能够自动进行聚类分析,识别文献中的主题或研究领域。通过调整算法参数,可优化聚类结果,帮助用户理解不同研究之间的关联性。时间切片分析:Citespace可基于文献的年份信息创建时间切片,分析特定领域在不同时间段内的发展变化。动态视图:除了静态网络图,Citespace还提供动态视图,便于用户观察网络结构随时间的演变,更好地把
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值