K小数查询 分块模板

43 篇文章 0 订阅
39 篇文章 6 订阅

题目大意

有n个数,两种操作
1:将x~y的数增加z
2:求x~y中的第k小数
输入第一个数为1就是操作1,2就是操作2,后面依次是x,y,z(k)

Sample Input

4
2 1 1 3
3
2 2 4 2
1 1 3 3
2 1 4 3

Sample Output

1
4

题解

对于分块的题,应该是要像建树那样建块的,包括确定每个块的范围和大小。大小一般为 n ,当然,也可以按照数据范围提前定好大小。
分块的基本思想:操作区间如果跨越了整个块,就给块打上标记。两边不足整个块的,暴力修改并维护。块的数量不超过 n ,暴力修改时不超过 2n ,也就是每次操作不超过 n 。查询一样。
加设块的大小为sz,s[i]表示块i的开头,t[i]表示块i的结尾,那么

s[i]=(i-1)*sz+1;t[i]=min(i*sz,n);

对于不同的题,维护不同的东西。

下面是这道题的题解

比如例题,需要维护的是每个块都是有序的。
那么先二分答案,对于每次修改,如果跨越了整个块,直接加到标记里面,不影响顺序。两边的线暴力修改,再重新排序两边的块。
查询也差不多,目的是找到小于二分的答案的数的数量,对于跨越的整个块就二分这个数量,因为是有序的。其余暴力,记得加上整个快的标记。

code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 80100
#define sz 300
using namespace std;
int n,add[N],s[N],t[N];
struct note{
    int d,w,s;
};
note a[N];
bool cnt(note x,note y){return x.d<y.d;}
int serch(int i,int k)
{
    int l=s[i],r=t[i];
    for(;l+1<r;)
    {
        int m=(l+r)/2;
        if(a[m].d+add[i]<k) l=m;else r=m;
    }
    while (a[r].d+add[i]>=k && r>=l) r--;
    return r-s[i]+1;
}
int get(int x,int y,int k)
{
    int l=(x+sz-1)/sz,r=(y+sz-1)/sz;
    int ans=0;
    if (l==r){
        fo(i,x,y) if (a[a[i].s].d+add[l]<k) ans++;
        return ans;
    }
    fo(i,l+1,r-1) 
    {
        ans+=serch(i,k);    
    }
    fo(i,x,t[l]) if (a[a[i].s].d+add[l]<k) ans++;
    fo(i,s[r],y) if (a[a[i].s].d+add[r]<k) ans++;
    return ans;
}
int main()
{
    scanf("%d",&n);
    fo(i,1,n) scanf("%d",&a[i].d),a[i].w=i;
    fo(i,1,(n+sz-1)/sz) 
    {
        s[i]=(i-1)*sz+1;t[i]=min(i*sz,n);
        sort(a+s[i],a+t[i]+1,cnt);
    }
    fo(i,1,n) a[a[i].w].s=i;
    int ak;scanf("%d",&ak);
    for(;ak;ak--)
    {
        int x,y,z,ko;scanf("%d%d%d%d",&ko,&x,&y,&z);
        if (ko==1)
        {
            int l=(x+sz-1)/sz,r=(y+sz-1)/sz;
            if (l==r) 
            {
                fo(i,x,y) a[a[i].s].d+=z;
                sort(a+s[l],a+t[l]+1,cnt);
                fo(i,s[l],t[l]) a[a[i].w].s=i;
                continue;
            }
            fo(i,l+1,r-1) add[i]+=z;
            fo(i,x,t[l]) a[a[i].s].d+=z;
            fo(i,s[r],y) a[a[i].s].d+=z;
            sort(a+s[l],a+t[l]+1,cnt);sort(a+s[r],a+t[r]+1,cnt);
            fo(i,s[l],t[l]) a[a[i].w].s=i;fo(i,s[r],t[r]) a[a[i].w].s=i;
        }
        else
        {
            int l=-5000000,r=5000000;
            for(;l+1<r;)
            {
                if (get(x,y,(l+r)/2)<=z-1) l=(l+r)/2;else r=(l+r)/2;
            }
            if (get(x,y,l)<=z) r=l;

            printf("%d\n",r);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值