题目大意
有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);
}
}
}