BZOJ 3110 [Zjoi2013]K大数查询 (整体二分 + 树状数组或线段树处理区间合值)

3110: [Zjoi2013]K大数查询


Description

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

Input

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

Output

输出每个询问的结果

Sample Input

2 5
1 1 2 1
1 1 2 2
2 1 1 2
2 1 1 1
2 1 2 3

Sample Output

1
2
1

HINT



【样例说明】

第一个操作 后位置 1 的数只有 1 , 位置 2 的数也只有 1 。 第二个操作 后位置 1

的数有 1 、 2 ,位置 2 的数也有 1 、 2 。 第三次询问 位置 1 到位置 1 第 2 大的数 是

1 。 第四次询问 位置 1 到位置 1 第 1 大的数是 2 。 第五次询问 位置 1 到位置 2 第 3

大的数是 1 。‍


N,M<=50000,N,M<=50000

a<=b<=N

1操作中abs(c)<=N

2操作中c<=Maxlongint


分析:

还是一个整体二分的做法,对于区间更新,用树状数组或者线段树去维护

对于c可以全部+N+1转换成正数方便处理,负数在区间直接/2会出bug,在结果处将答案转换过来

另外就是要开long long

说实在的整体二分对手速的要求和仔细程度还真是高,毕竟是离线的不是很好找bug


#include<cstring>
#include<cstdio>
#include<algorithm>
#include<iostream>

using namespace std;

#define maxn 50005

int n,m;
long long ans[maxn];
struct node
{
    int kind;
    int l,r;
    long long k;
    int index;
} q[maxn],q1[maxn],q2[maxn];

long long c1[maxn],c2[maxn];

inline int lowbit(int x)
{
    return x&-x;
}

inline void update(int x,long long val)
{
    for(int i=x; i<=n; i+=lowbit(i))
    {
        c1[i]+=val;
        c2[i]+=(x-1)*val;
    }
}

inline long long query(int x)
{
    long long sum1=0,sum2=0;
    for(int i=x; i>0; i-=lowbit(i))
    {
        sum1+=c1[i];
        sum2+=c2[i];
    }
    return sum1*x-sum2;
}

void divide(int s,int t,long long l,long long r)
{
    if(s>t) return ;
    if(l==r)
    {
        for(int i=s; i<=t; i++)
        {
            if(q[i].kind==2)
            {
                ans[ q[i].index ]=l;
            }
        }
        return ;
    }
    long long mid=(l+r)/2;
    int num1=0,num2=0,flag1=0,flag2=0;
    for(int i=s; i<=t; i++)
    {
        if(q[i].kind==1)
        {
            if(q[i].k<=mid)
            {
                update(q[i].l,1);
                update(q[i].r+1,-1);
                q1[num1++]=q[i];
            }
            else q2[num2++]=q[i];
        }
        else
        {
            long long tmp=query(q[i].r)-query(q[i].l-1);
            if(tmp>=q[i].k) q1[num1++]=q[i],flag1=1;
            else q[i].k-=tmp,q2[num2++]=q[i],flag2=1;
        }
    }
    for(int i=s; i<=t; i++)
    {
        if(q[i].kind==1)
        {
            if(q[i].k<=mid)
            {
                update(q[i].l,-1);
                update(q[i].r+1,1);
            }
        }
    }
    for(int i=0; i<num1; i++) q[i+s]=q1[i];
    for(int i=0; i<num2; i++) q[i+s+num1]=q2[i];
    if(flag1) divide(s,s+num1-1,l,mid);
    if(flag2) divide(s+num1,t,mid+1,r);
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(c1,0,sizeof c1);
        memset(c2,0,sizeof c2);
        int num=0;
        for(int i=1; i<=m; i++)
        {
            scanf("%d%d%d%lld",&q[i].kind,&q[i].l,&q[i].r,&q[i].k);
            if(q[i].kind==1)
            {
                q[i].k+=n+1;
                q[i].k=2*n+2-q[i].k;
            }
            else if(q[i].kind==2) q[i].index=num++;
        }
        divide(1,m,1,2*n+2);
        for(int i=0; i<num; i++)
        {
            printf("%lld\n",(long long)n+1-ans[i]);
        }
    }
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值