【BZOJ3110】 K大数查询(整体二分+线段树)

这篇博客介绍了一种利用整体二分和线段树数据结构解决区间插入和查询问题的方法。通过建立线段树并结合整体二分策略,可以高效地处理区间内的数据操作,时间复杂度为O(nlog^2n)。文章给出了详细的代码实现和样例解析,帮助读者理解如何在实际编程中应用这一技术。
摘要由CSDN通过智能技术生成

题目描述

N N N个位置, M M M个操作。操作有两种,每次操作如果是1 a b c的形式表示在第 a a a个位置到第 b b b个位置,每个位置加入一个数 c c c。如果是2 a b c,表示询问从第 a a a个位置到第 b b b个位置,第 c c c大的数是多少。

输入格式

第一行 N , M N,M N,M,接下来 M M M行,每行形如1 a b c2 a b c

输出格式

输出每个询问的结果

样例
样例输入
2 5
1 1 2 1
1 1 2 2
2 1 1 2
2 1 1 1
2 1 2 3
样例输出
1
2
1

题解

前置知识:整体二分

如果掌握了整体二分,这道题就会变得十分简单。

首先,整体二分中有插入、删除和查询三种操作,但这道题显然只需要插入和查询两种操作。不过,整体二分模板中的插入是单点插入,但这题是区间插入。所以我们就要将维护当前状态的数状数组改为线段树,这样维护当前状态有利于区间修改。其他部分与整体二分模板相同。时间复杂度为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

code

#include<bits/stdc++.h>
#define lc k<<1
#define rc k<<1|1
using namespace std;
int n,m,gs;
long long num[50005];
struct node{
    int x,y,tp,id,ans;
    long long z,now;
}w[100005],w1[100005],w2[100005];
struct tree{
    long long sum,ly;
}tr[1000005];
bool cmp(node ax,node bx){
    return ax.id<bx.id;
}
void down(int k,int l,int r){
    int mid=(l+r)/2;
    tr[lc].sum+=(mid-l+1)*tr[k].ly;
    tr[rc].sum+=(r-mid)*tr[k].ly;
    tr[lc].ly+=tr[k].ly;
    tr[rc].ly+=tr[k].ly;
    tr[k].ly=0;
}
void ch(int k,int l,int r,int x,int y,int a){
    if(l>=x&&r<=y){
        tr[k].sum+=(r-l+1)*a;tr[k].ly+=a;return;
    }
    if(l==r) return;
    down(k,l,r);
    int mid=(l+r)/2;
    if(x<=mid) ch(lc,l,mid,x,y,a);
    if(y>mid) ch(rc,mid+1,r,x,y,a);
    tr[k].sum=tr[lc].sum+tr[rc].sum;
}
long long find(int k,int l,int r,int x,int y){
    if(l>=x&&r<=y){
        return tr[k].sum;
    }
    if(l==r) return 0;
    down(k,l,r);
    int mid=(l+r)/2;
    long long re=0;
    if(x<=mid) re+=find(lc,l,mid,x,y);
    if(y>mid) re+=find(rc,mid+1,r,x,y);
    return re;
}
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==2) w[i].ans=l;
        }
        return;
    }
    int mid=(l+r)/2;
    int l1=0,r1=0;
    for(int i=h;i<=t;i++){
        if(w[i].tp==1){
            
            if(w[i].z<=mid){
                w1[++l1]=w[i];
                ch(1,1,n,w[i].x,w[i].y,1);
            }
            else w2[++r1]=w[i];
        }
        else{
            long long v=find(1,1,n,w[i].x,w[i].y);
            if(w[i].now+v>=w[i].z) w1[++l1]=w[i];
            else{
                w[i].now+=v;
                w2[++r1]=w[i];
            }
        }
    }
    for(int i=h;i<=t;i++){
        if(w[i].tp==1&&w[i].z<=mid){
            ch(1,1,n,w[i].x,w[i].y,-1);
        }
    }
    for(int i=1;i<=l1;i++) w[h+i-1]=w1[i];
    for(int i=1;i<=r1;i++) w[h+l1+i-1]=w2[i];
    gt(h,h+l1-1,l,mid);gt(h+l1,t,mid+1,r);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d%lld",&w[i].tp,&w[i].x,&w[i].y,&w[i].z);w[i].id=i;
        if(w[i].tp==1) num[++gs]=w[i].z;
    }
    sort(num+1,num+gs+1);
    gs=unique(num+1,num+gs+1)-num-1;
    for(int i=1;i<=m;i++){
        if(w[i].tp==1){
            w[i].z=lower_bound(num+1,num+gs+1,w[i].z)-num;w[i].z=gs-w[i].z+1;
        }//这里将第k大转化为第k小,在最后输出中再换回来
    }
    gt(1,m,1,gs);
    sort(w+1,w+m+1,cmp);
    for(int i=1;i<=m;i++){
        if(w[i].tp==2) printf("%lld\n",num[gs-w[i].ans+1]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值