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

本文介绍了一种使用分块技术解决特定区间操作问题的方法。通过对序列进行分块并维护每块的数据结构,可以高效地处理区间加法和查询第k小元素的操作。文章详细解释了算法原理,并提供了完整的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

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

  • 1 L R xLR加上x
    • 2 L R kLRk小的数

    Solution

    分块大法好!

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

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

关键在查询!

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

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

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

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

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

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

所以二分出来的一定是s

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); 
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值