[ZJOI2013]K大数查询

有N个位置,M个操作。操作有两种,每次操作是:

  • 1 a b c:表示在第a个位置到第b个位置,每个位置加上一个数c
  • 2 a b c:表示询问从第a个位置到第b个位置,第C大的数是多少。

算法见注释

#include<cstdio>
#define int long long
using namespace std;
const int N=5e4+5;
int n,m,qqq;

struct data{
    int t,a,b,c,idx;
    void read(){
        scanf("%lld%lld%lld%lld",&t,&a,&b,&c);
        if(t==2)idx=++qqq;
    }
};
data q[N];
int ans[N];

struct BIT{//树状数组
    int c[N];
    void add(int pos,int v){
        for(int i=pos;i<=n;i+=i&-i)c[i]+=v;
    }
    int pre(int pos){
        int res=0;
        for(int i=pos;i>=1;i-=i&-i)res+=c[i];
        return res;
    }
};
BIT d,dm;
void add(int l,int r,int v){
    d.add(l,v), d.add(r+1,-v);
    dm.add(l,v*l), dm.add(r+1,-v*(r+1));
}
int pre(int pos){return (pos+1)*d.pre(pos)-dm.pre(pos);}
int sum(int l,int r){return pre(r)-pre(l-1);}

data q1[N],q2[N];
void solve(int l,int r,int L,int R){
    if(L==R){
        for(int i=l;i<=r;i++)if(q[i].t==2)ans[q[i].idx]=L;
        return;
    }
    int Mid=(L+R)>>1,l1=0,l2=0;
    for(int i=l;i<=r;i++){//计算在[Mid+1,R]区间的数的个数(contri)
        data now=q[i];
        if(now.t==1){
            //只加入[Mid+1,R]的贡献
            if(Mid<now.c&&now.c<=R)add(now.a,now.b,1), q2[++l2]=now;
            else q1[++l1]=now;
        } else {
            int t=sum(now.a,now.b);
            if(now.c<=t)q2[++l2]=now;
            else now.c-=t, q1[++l1]=now;
        }
    }
    for(int i=1;i<=l2;i++)if(q2[i].t==1)add(q2[i].a,q2[i].b,-1);//还原
    
    for(int i=1;i<=l1;i++)q[l+i-1]=q1[i];
    for(int i=1;i<=l2;i++)q[l+l1+i-1]=q2[i];
    solve(l,l+l1-1,L,Mid);
    solve(l+l1,r,Mid+1,R);
}
signed main(){
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=m;i++)q[i].read();
    solve(1,m,0,n);
    for(int i=1;i<=qqq;i++)printf("%lld
",ans[i]);
    return 0;
}
/*
 * 考虑整体二分。二分答案后,问题转化为
 * - 查询比mid大的数
 * - 区间添加数
 * 整体二分是要求贡献独立的,因此在二分的过程中只考虑跨区间的贡献,并且
 * 必须全部统计完
 */

原文地址:https://www.cnblogs.com/sshwy/p/10993327.html