K 大数查询

题目大意

有N个集合,初始为空。有M个操作,
修改操作:编号范围在l~r的集合都加入一个数值为a的数,
询问操作:编号范围在l~r的集合数值为第k大的数。
n,m<=50000,|a|<=n,k

树套树

当然可行,但我不会

考虑离线——整体二分

L,R表示数值的区间,mid=(L+R)/2。
用线段树维护在编号范围在l~r的集合中数值范围在mid+1~R的数的个数
修改操作的a的数值若>mid则编号范围在l~r(修改的范围)的数的个数+1,并把该操作传到mid+1~R否则传到L~mid的区间
询问操作直接得到编号范围在l~r的数的个数,若>k则把该操作传到mid+1~R的区间否则传到L~mid的区间

注意

每次线段树要清空,要打标记清空,否则会很慢。

代码

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=50010;
struct pc{
    int id,l,r,c,p,b;
} a[maxn],a1[maxn],a2[maxn];
int tree[6*maxn],bz[6*maxn],ans[maxn],n;
bool b[6*maxn],bk[maxn];
void down(int k,int l,int r)
{   int m=(l+r)/2;
    if (b[k]){
        b[k*2]=b[k*2+1]=1;
        b[k]=bz[k*2]=bz[k*2+1]=tree[k*2]=tree[k*2+1]=0;
    }
    if (bz[k]){
        tree[k*2]+=bz[k]*(m-l+1);
        tree[k*2+1]+=bz[k]*(r-m);
        bz[k*2]+=bz[k];bz[k*2+1]+=bz[k];bz[k]=0;
    }
}
void change(int k,int l,int r,int a,int b)
{
    if (l==a&&r==b){
        tree[k]+=r-l+1;
        bz[k]++;
        return;
    }
    down(k,l,r);
    int m=(l+r)/2;
    if (b<=m) change(k*2,l,m,a,b); else
    if (a>m) change(k*2+1,m+1,r,a,b); else{
        change(k*2,l,m,a,m);change(k*2+1,m+1,r,m+1,b);
    }
    tree[k]=tree[2*k]+tree[2*k+1];
}
int find(int k,int l,int r,int a,int b)
{   if (l==a&&r==b) return tree[k];
    down(k,l,r);
    int m=(l+r)/2;
    if (b<=m) return find(k*2,l,m,a,b); else
    if (a>m) return find(k*2+1,m+1,r,a,b); else
    return find(k*2,l,m,a,m)+find(k*2+1,m+1,r,m+1,b);
}
void cdq(int x,int y,int l,int r)
{
    if (x>y) return;
    int i,m=(l+r+2*n)/2-n;
    if (l==r){
        for (i=x;i<=y;i++) ans[a[i].id]=l;
        return;
    }
    b[1]=1;
    bz[1]=0;tree[1]=0;
    for (i=x;i<=y;i++){
        if (a[i].b){
            int j=find(1,1,n,a[i].l,a[i].r);a[i].p=1;
            if (j<a[i].c) {
                a[i].p=0;
                a[i].c-=j;
            }
            continue;
        }       
        if (a[i].c>m)
        {   change(1,1,n,a[i].l,a[i].r);
            a[i].p=1;
        }
    }int t=0;
    for (i=x;i<=y;i++){
        if (!a[i].p)a1[++t]=a[i];else a2[i-x+1-t]=a[i];
    }
    for (i=x;i<=y;i++) {
        if (i-x+1<=t) a[i]=a1[i-x+1];else a[i]=a2[i-x+1-t];
        a[i].p=0;
    }
    cdq(x,x+t-1,l,m);cdq(x+t,y,m+1,r);
}
int main(){ 
    int m;
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {   scanf("%d%d%d%d",&a[i].b,&a[i].l,&a[i].r,&a[i].c);a[i].id=i;a[i].p=0;
        a[i].b--;bk[i]=a[i].b;
    }
    cdq(1,m,-n,n);
    for (int i=1;i<=m;i++)if (bk[i]) printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值