[BZOJ3110][ZJOI2013]K大数查询-CDQ分治-整体二分

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


仿佛看见了树套树……
然而讨论区发现可以用整体二分……


思路:
可以说是CDQ分治和整体二分的结合吧?

考虑整体二分,每次二分一个答案,并维护答案在当前二分区间内的询问。
维护一个树状数组来描述每个位置有多少数大于当前二分的答案,需要支持区间修改和查询。
根据CDQ分治的思想,考虑按时间顺序扫描当前区间内的所有操作。
如果是添加数,若添加的数比二分的值大,则进行一次区间+1并将这个询问丢到右半边,否则直接丢到左半边。
如果是询问,如果区间的和大于当前查询的排名,则直接丢到右边,否则将当前所查询的排名值减去当前查询结果,并丢到左边。
然后一层层递归分治下去即可~
注意,答案会炸int,需使用unsigned int或long long。

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

using namespace std;

typedef long long ll;
const ll N=50009;

inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0' || '9'<ch){if(ch=='-')f=-1;ch=getchar();}
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x*f;
}

struct op
{
    ll id,ty,a,b,c;
    bool operator < (op o)const{return id<o.id;}
}q[N],tmpl[N],tmpr[N];

ll n,m,atop;
ll ans[N];
ll bit[N],bits[N];

inline void modify(ll p,ll v)
{
    for(ll i=p;i<=n;i+=(i&-i))
        bit[i]+=v,bits[i]+=p*v;
}

inline ll query(ll p)
{
    ll ret=0;
    for(ll i=p;i;i-=(i&-i))
        ret+=(p+1)*bit[i]-bits[i];
    return ret;
}

inline ll qrange(ll l,ll r)
{
    return query(r)-query(l-1);
}

inline void cdq(ll al,ll ar,ll l,ll r)
{
    if(l==r)
    {
        for(ll i=al;i<=ar;i++)
            if(q[i].ty==2)
                ans[q[i].id]=l;
        return;
    }

    ll mid=l+r>>1,lt=0,rt=0;ll ret;
    for(ll i=al;i<=ar;i++)
        if(q[i].ty==1)
        {
            if(mid<q[i].c)
            {
                modify(q[i].a,1);
                modify(q[i].b+1,-1);
                tmpr[++rt]=q[i];
            }
            else
                tmpl[++lt]=q[i];
        }
        else
        {
            if((ret=qrange(q[i].a,q[i].b))>=q[i].c)
                tmpr[++rt]=q[i];
            else
            {
                q[i].c-=ret;
                tmpl[++lt]=q[i];
            }
        }

    ll amid=al+lt-1;
    for(ll i=al;i<=amid;i++)
        q[i]=tmpl[i-al+1];
    for(ll i=amid+1;i<=ar;i++)
    {
        q[i]=tmpr[i-amid];
        if(q[i].ty==1)
        {
            modify(q[i].a,-1);
            modify(q[i].b+1,1);
        }
    }

    cdq(al,amid,l,mid);
    cdq(amid+1,ar,mid+1,r);
}

int main()
{
    n=read();m=read();
    for(ll i=1;i<=m;i++)
    {
        q[i].ty=read();
        q[i].a=read();
        q[i].b=read();
        q[i].c=read();
        if(q[i].ty==2)
            q[i].id=++atop;
    }

    cdq(1,m,1,n);
    for(ll i=1;i<=atop;i++)
        printf("%lld\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值