长为nnn的数列,操作涉及区间修改和区间查询,应该如何处理?
用线段树等许多数据结构都可以做,但是有时用其它数据结构来解决会十分棘手,代码量也会变得很大。这时,我们就需要用到分块了。
分块
分块是一种非常暴力的数据结构。对于一个长度为nnn的序列,将序列分成⌈n⌉\lceil\sqrt n\rceil⌈n⌉块,每块长度最多为⌊n⌋\lfloor\sqrt n\rfloor⌊n⌋,维护每一块的信息,我们设第iii个块的左端点为LiL_iLi,右端点为RiR_iRi
单点查询直接查询就可以了,是O(1)O(1)O(1)的。单点修改需要修改点和该点所在的区间,一般情况下也可以达到O(1)O(1)O(1)
接下来就是区间查询和区间修改了。比如要将区间[l,r][l,r][l,r]进行区间查询,先找到lll所在的块aaa和rrr所在的块bbb。对[l,Ra][l,R_a][l,Ra]和[Lb,r][L_b,r][Lb,r]暴力查询,然后在第a+1a+1a+1到b−1b-1b−1块上打懒标记或每块逐个处理即可。区间修改与区间查询类似。
对于单次操作,枚举两边多余部分O(n)O(\sqrt n)O(n),枚举各块O(n)O(\sqrt n)O(n)。所以一次操作的时间复杂度为O(n)O(\sqrt n)O(n),总时间复杂度O(nn)O(n\sqrt n)O(nn)。虽然相对于线段树的O(nlogn)O(nlogn)O(nlogn)分块还是略逊一筹,但是分块方便易懂,在数据规模较小的情况下,分块甚至能代替许多高级的数据结构。
例题
例1
给出一个长为nnn的数列,以及nnn次操作,操作涉及区间加法,单点查值。
板题,比较简单
code
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int n,v,op,l,r,c,a[1000005],k[100005];
void pt(){
if(l%v!=1){
int re=(l-1)/v*v+v;
while(l<=r&&l<=re){
a[l]+=c;++l;
}
}
while(l/v<=r/v&&r-l+1>=v){
k[(l-1)/v+1]+=c;l+=v;
}
while(l<=r){
a[l]+=c;++l;
}
}
int main()
{
scanf("%d",&n);
v=sqrt(n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){
scanf("%d%d%d%d",&op,&l,&r,&c);
if(op==0) pt();
else printf("%d\n",a[r]+k[(r-1)/v+1]);
}
}
例2
给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的元素个数。
同样地,将数列分为⌈n⌉\lceil\sqrt n\rceil⌈n⌉块,用数组aaa来按原序存储数列,用数组ttt来按各块排序后的顺序存储数组。
对于区间加法,将两边部分在aaa数组中一个一个处理,然后将两边部分所在的块放在ttt数组中重新排序。中间各块打上懒标记即可。
对于区间查询,两边部分枚举一下,中间各块每块在ttt数组中二分查找即可
总时间复杂度O(nlognn)O(nlogn\sqrt n)O(nlognn),具体方法看代码
code
#include<bits/stdc++.h>
using namespace std;
int n,v,op,l,r,ans;
long long c,a[200005],t[200005],k[10005];
int main()
{
scanf("%d",&n);
v=sqrt(n);
for(int i=n+1;i<=n+v;i++) a[i]=t[i]=1e15;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
t[i]=a[i];
}
for(int i=1;i<=n;i++){
if(i%v==1){
sort(t+i,t+i+v);
}
}
for(int i=1;i<=n;i++){
scanf("%d%d%d%lld",&op,&l,&r,&c);
if(op==0){
if(l%v!=1){
int re=(l-1)/v*v+v;
while(l<=r&&l<=re){
a[l]+=c;++l;
}
for(int i=re-v+1;i<=re;i++) t[i]=a[i];
sort(t+re-v+1,t+re+1);
}
while(l/v<=r/v&&r-l+1>=v){
k[(l-1)/v+1]+=c;l+=v;
}
if(l<=r){
int re=(l-1)/v*v;
while(l<=r){
a[l]+=c;++l;
}
for(int i=re+1;i<=re+v;i++) t[i]=a[i];
sort(t+re+1,t+re+v+1);
}
}
else{
ans=0;
if(l%v!=1){
int re=(l-1)/v*v+v;
while(l<=r&&l<=re){
if(a[l]<c*c-k[(l-1)/v+1]) ++ans;
++l;
}
}
while(l/v<=r/v&&r-l+1>=v){
int dl=(l-1)/v*v+1,dr=(l-1)/v*v+v,mid,ot=(l-1)/v*v,re=(l-1)/v+1;
while(dl<=dr){
mid=(dl+dr)/2;
if(t[mid]<c*c-k[re]){
ot=mid;dl=mid+1;
}
else dr=mid-1;
}
ans+=ot-(l-1)/v*v;l+=v;
}
if(l<=r){
int re=(l-1)/v+1;
while(l<=r){
if(a[l]<c*c-k[re]) ++ans;
++l;
}
}
printf("%d\n",ans);
}
}
}