[JZOJ4438] K小数查询(经典分块)

Description

给你 N 个数组成的序列,需要支持两种操作

  • 1 L R x L R 加上x

    • 2 L R k L R k 小的数

    Solution

    分块大法好!

    我们将序列分成N块,每块中维护原来的顺序的值,以及将该块所有值排序后的值,并且每个值还带有一个指针指向对应的那个值

    修改整块的就直接打标记,两边的暴力重构该块

    关键在查询!

    我们可以二分一个答案(思考一下,二分出来的答案会不会序列中不存在呢?)

    然后转化成判定性问题,判断二分出来的答案是不是在第 k

    只需要对于每个块,找该块中小于这个值的个数,全部加起来看是不是k1,然后逼近答案。

    证明一下这样为什么不会出现不存在的答案

    假设正确答案是 s ,那么二分出大于s的答案显然不可能,因为个数一定要加上 s 本身这一个

    小于等于s,都有可能等于 k1 ,然而二分出来的答案是这些值中的最大值(自己想为什么)

    所以二分出来的一定是 s <script type="math/tex" id="MathJax-Element-24">s</script>

    Code

    #include<cstdio>
    #include<cstdlib>
    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cmath>
    #define fo(i,a,b) for(i=a;i<=b;i++)
    #define fod(i,a,b) for(i=a;i>=b;i--)
    #define mo 1000000007
    using namespace std;
    int pl[500],wz[80605],n,m,size;
    struct note
    {
        int x,y;
    }a[80605],b[80605];
    bool cmp(note x,note y)
    {
        return x.x<y.x; 
    }
    int nt(int k)
    {
        return ((k-1)/size+1)*size+1;
    }
    int pd(int l,int r,int k)
    {
        int p=nt(l),sum=0,i;
        note k1;
        k1.x=k;
        while (nt(p)<r+1)
        {
            k1.x-=pl[wz[p]];
            sum+=lower_bound(b+p,b+nt(p),k1,cmp)-b-p;
            k1.x+=pl[wz[p]];
            p=nt(p); 
        } 
        if (nt(l)>r)
        {
            fo(i,l,r) if (a[i].x<k-pl[wz[i]]) sum++;
        }
        else 
        {
            fo(i,l,nt(l)-1) if (a[i].x<k-pl[wz[i]]) sum++;
            fo(i,p,r) if (a[i].x<k-pl[wz[i]]) sum++;
        }
        return sum;
    
    }
    int main()
    {
        int i,j;
        cin>>n;
        size=(int)sqrt(n);
        fo(i,1,n)
        {
            scanf("%d",&a[i].x);
            b[i].x=a[i].x;
            b[i].y=i;
        } 
        j=1;
        wz[j]=1;
        while (j+size<=n+1)
        {
            sort(b+j,b+j+size,cmp);
            j+=size;
            wz[j]=wz[j-size]+1;
        }
        if(j!=n+1) sort(b+j,b+n+1,cmp);
        fo(i,1,n) 
        {
            if (wz[i]==0) wz[i]=wz[i-1]; 
            a[b[i].y].y=i;
        }
        cin>>m;
        int i1=0;
        fo(i,1,m)
        {
            int p,l,r,k,j;
            scanf("%d%d%d%d",&p,&l,&r,&k);
            if (p==1)
            {
                j=nt(l); 
                while (nt(j)<=r)
                {
                    pl[wz[j]]+=k;
                    j=nt(j);
                } 
                int q;
                if (nt(l)>r)
                {
                    fo(q,l,r) a[q].x=b[a[q].y].x=a[q].x+k;
                    sort(b+nt(l)-size,b+nt(l),cmp);
                    fo(q,nt(l)-size,nt(l)-1) a[b[q].y].y=q;
                }
                else 
                {
                    fo(q,l,nt(l)-1) a[q].x=b[a[q].y].x=a[q].x+k;
                    sort(b+nt(l)-size,b+nt(l),cmp);
                    fo(q,nt(l)-size,nt(l)-1) a[b[q].y].y=q;
    
                    fo(q,j,r) a[q].x=b[a[q].y].x=a[q].x+k;
                    sort(b+j,b+nt(j),cmp);
                    fo(q,j,nt(j)-1) a[b[q].y].y=q;
                }
            }
            else
            {
                 int x=-5000000,y=5000000,mid;
                 while (x<y-1)
                 { 
                    mid=(x+y)/2;
                    if (pd(l,r,mid)<=k-1) x=mid;
                    else y=mid;
                 }
                 if (pd(l,r,y)==k-1) printf("%d\n",y);
                 else printf("%d\n",x); 
            }
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值