bzoj3110 K大数查询

传送门
找第 K K K大的时候把大于 m i d mid mid的标记为1.
如果把大于等于 m i d mid mid的标为1,那么有可能第 K K K大就是 m i d mid mid而当前统计的 1 1 1的个数大于 K K K(即有多个数为 m i d mid mid),于是答案会被往上调(为了减少 1 1 1的个数)。
也就是说答案范围变为 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]。本来答案是 m i d mid mid。就出锅了。

比如说:3,4,4,4,5 求1~5中的第三大。(显然是4)
假设当前 m i d mid mid为4,于是数列变成0,1,1,1,1。
由于 4 > 3 4>3 4>3,那么二分的答案要上调,要把答案锁定在 [ m i d + 1 , 5 ] [mid+1,5] [mid+1,5]
于是得到了错误答案。

所以要把大于 m i d mid mid的标为1。

具体做法是:把操作按照答案范围分为两部分。
修改操作就按照修改值与 m i d mid mid大小比较,因为对应修改值只会在对应答案范围内产生贡献。

询问 k k k大就按照当前的操作顺序一步一步操作,然后遇见询问操作就统计区间和。和大了就要把答案上调。和小了就先减去这部分和(比它大的数的个数),然后把答案调小。

然后做完之后要把影响消回来,就是代码中区间减的部分。

50000次给50000个位置加上一个数。于是线段树统计sum爆int
输入有负数,注意读优

#include<bits/stdc++.h>
#define lc (root<<1)
#define rc (root<<1|1)
#define len(x) (T[x].r-T[x].l+1)
#define mid ((T[root].l+T[root].r)>>1)
#define ll long long
using namespace std;
const int maxn=5e4+10;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return x*f;
}
int n,m,op,a,b,c,cnt,ans[maxn];
struct que{int op,l,r,id;ll k;}Q[maxn],Q1[maxn],Q2[maxn];
struct node{int l,r;ll add,sum;}T[maxn<<2];
inline void pushup(int root){T[root].sum=T[lc].sum+T[rc].sum;}
inline void pushnow(int root,int val){
    T[root].add+=val;
    T[root].sum+=len(root)*val;
}
inline void pushdown(int root){
    if(T[root].add){
        pushnow(lc,T[root].add);
        pushnow(rc,T[root].add);
        T[root].add=0;
    }
}
inline void Update(int root,int l,int r,int val){
    if(l<=T[root].l&&T[root].r<=r)
        return pushnow(root,val);
    pushdown(root);
    if(l<=mid) Update(lc,l,r,val);
    if(r> mid) Update(rc,l,r,val);
    pushup(root);
}
inline ll Query(int root,int l,int r,ll ret=0){
    if(l<=T[root].l&&T[root].r<=r)
        return T[root].sum;
    pushdown(root);
    if(l<=mid) ret+=Query(lc,l,r);
    if(r> mid) ret+=Query(rc,l,r);
    return ret;
}
inline void build(int root,int l,int r){
    T[root].l=l,T[root].r=r;
    if(l==r) return;
    build(lc,l,mid),build(rc,mid+1,r);
}
inline void Solve(int l,int r,int S,int T){
    if(l>r||S>T) return;
    if(l==r){
        for(int i=S;i<=T;++i) if(Q[i].op==2)
            ans[Q[i].id]=l;
        return;
    }
    int Mid=(l+r)>>1,num1=0,num2=0;
    for(int i=S;i<=T;++i){
        if(Q[i].op==1){
            if(Q[i].k>Mid)
                Update(1,Q[i].l,Q[i].r,1),Q2[++num2]=Q[i];
            else Q1[++num1]=Q[i];
        }
        else{
            ll tmp=Query(1,Q[i].l,Q[i].r);
            if(tmp>=Q[i].k) Q2[++num2]=Q[i];
            else Q[i].k-=tmp,Q1[++num1]=Q[i];
        }
    }
    for(int i=1;i<=num2;++i) if(Q2[i].op==1) Update(1,Q2[i].l,Q2[i].r,-1);
    for(int i=1;i<=num1;++i) Q[S+i-1]=Q1[i];
    for(int i=1;i<=num2;++i) Q[S+num1+i-1]=Q2[i];
    Solve(l,Mid,S,S+num1-1),Solve(Mid+1,r,S+num1,T);
}
int main(){
    n=read(),m=read(),build(1,1,n);
    for(int i=1;i<=m;++i){
        Q[i].op=read(),Q[i].l=read(),Q[i].r=read(),Q[i].k=(ll)read();
        if(Q[i].op==2) Q[i].id=++cnt;
    }
    Solve(1,n,1,m);
    for(int i=1;i<=cnt;++i)
        printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值