bzoj3110 [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

Source

[ Submit][ Status][ Discuss]



分析: 树套树(主席树+线段树?)
不,我们用整体二分

之前做过K大数:poj2104
这道题的不同之处就在于修改的是一个区间
所以我们考虑用线段树代替树状数组

询问的是第K大:从大到小排序后,排名第K的数
二分答案M,每次把插入值大于M的修改插入到线段树中
处理完询问后,不要忘了把消除区间影响

tip

所有的操作有一种潜在的规律:时间
一开始把t1,t2写错了(手癌晚期。。。药丸)

< 还是 <= ,> 还是 >=,要想明白(暴力对着样例调一调吧)
开ll

对于消除区间影响,学姐有一种很好的方法:
因为每次solve的时候线段树中是没有修改值的(所有值都是0)
我采用的方法是修改了哪些区间,就消除哪些区间的影响
实际上我们可以直接在根结点上打一个清空标记
从上到下访问的时候,pushdown即可

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long

using namespace std;

const ll INF=1e9;
const int N=500005;
struct node{
    int x,y,type,id,num;
    ll z;
};
node a[N],q1[N],q2[N];
int n,m,tt=0;
ll maxx,minn,ans[N],tree[N<<2],ad[N<<2];

void push(int bh,int l,int r)
{
    int L=bh<<1,R=bh<<1|1,mid=(l+r)>>1;
    if (ad[bh]!=0&&l!=r)
    {
        tree[L]+=(ll)(mid-l+1)*ad[bh];
        tree[R]+=(ll)(r-mid)*ad[bh];
        ad[L]+=ad[bh]; ad[R]+=ad[bh];
        ad[bh]=0;
    }
}

ll ask(int bh,int l,int r,int L,int R)
{
    push(bh,l,r);
    if (l>=L&&r<=R)
        return tree[bh];
    int mid=(l+r)>>1;
    ll ans=0;
    if (L<=mid) ans+=ask(bh<<1,l,mid,L,R);
    if (R>mid) ans+=ask(bh<<1|1,mid+1,r,L,R);
    return ans; 
}

void add(int bh,int l,int r,int L,int R,ll z)
{
    push(bh,l,r);
    if (l>=L&&r<=R)
    {
        tree[bh]+=(ll)(r-l+1)*z;
        ad[bh]=z;
        return;
    }
    int mid=(l+r)>>1;
    if (L<=mid) add(bh<<1,l,mid,L,R,z);
    if (R>mid) add(bh<<1|1,mid+1,r,L,R,z);
    tree[bh]=tree[bh<<1]+tree[bh<<1|1];
}

void solve(int l,int r,ll L,ll R)
{
    if (L==R)
    {
        for (int i=l;i<=r;i++) 
            if (a[i].type==2) ans[a[i].id]=L;
        return;
    }

    ll M=(L+R)>>1;
    int t1=0,t2=0;
    for (int i=l;i<=r;i++)
    {
        if (a[i].type==1)   //insert
        {
            if (a[i].z>M)   //
            {
                add(1,1,n,a[i].x,a[i].y,1); 
                q2[++t2]=a[i]; 
            }
            else q1[++t1]=a[i];
        }
        else
        {
            ll sum=ask(1,1,n,a[i].x,a[i].y);
            if (sum<a[i].z) a[i].z-=sum,q1[++t1]=a[i];   //比M大的太少了 
            else q2[++t2]=a[i];
        }
    }

    for (int i=1;i<=t1;i++) a[l+i-1]=q1[i];
    for (int i=1;i<=t2;i++) a[l+t1+i-1]=q2[i];
    for (int i=l+t1;i<=r;i++) if (a[i].type==1) add(1,1,n,a[i].x,a[i].y,-1);   //清除影响 

    solve(l,l+t1-1,L,M);
    solve(l+t1,r,M+1,R);
}

int main()
{
    scanf("%d%d",&n,&m);
    maxx=-INF; minn=INF;
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d%lld",&a[i].type,&a[i].x,&a[i].y,&a[i].z);
        if (a[i].type==1) maxx=max(maxx,a[i].z),minn=min(minn,a[i].z);
        if (a[i].type==2) a[i].id=++tt;
    }
    solve(1,m,minn,maxx);   
    for (int i=1;i<=tt;i++) printf("%lld\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值