有时对于一些区间操作,比如求区间的最大(小)值、和、乘积等,如果朴素地求解,每次操作需要 O ( n ) O(n) O(n)的时间(n为区间长度)。如果需要多次求解,那么时间复杂度会很不理想。这时就需要一些特殊方法。
其中一种比较好理解的方法就是分块。就是把数据分成若干块,对于区间[l,r]上的操作,对其在完整的块内的部分直接求解,不完整的块单独求解。
一般地,我们把每块长度设为b=
[
n
]
[\sqrt n]
[n] ,这样就有n/b块。
在最坏情况下(l为所在区间的第二个,r为所在区间的倒数第二个。比如100个元素,10个一组,[l,r]=[2,99])查询和更新值都大约要计算
2
n
∼
3
n
2\sqrt n\sim 3\sqrt n
2n∼3n次。因此两操作的复杂度都是
O
(
n
)
O(\sqrt n)
O(n)。
以下分块支持两种操作:
1.给定s,t,求 a s , a s + 1 . . . a t a_s,a_{s+1} ... a_t as,as+1...at的和
2.给定s,t,x,把 a s , a s + 1 . . . a t a_s,a_{s+1} ... a_t as,as+1...at的值加上x
在更新时,由于对在完整的块内的部分的操作是整体求解的,所以必须设置一个标记数组,用于标记整个块被加过的值之和(注意不要乘以块长)。对于l,r在同一个块内的情况,要特别处理。
对于l和r对应的块,可以分完全包含和不完全包含两种情况处理。
为方便起见,数组与下标与块的编号都从0开始。这样
a
i
a_i
ai就在第i/b块中。
实现如下:
#include <cstdio>
#include <algorithm>
#include <cmath>
#define long long long
const int M=1e5+1,MAX_SEG=1+(int)sqrt(M);
using namespace std;
int a[M],n;
struct block {
long dat[MAX_SEG],addded[MAX_SEG];
int num_blo,blo_size;
block() {fill(dat,dat+MAX_SEG,0);}
};
block b;
inline int st_index(int blo) {return blo*(b.blo_size);} //计算每段开始元素
inline int end_index(int blo) {return st_index(blo+1)-1;} //计算每段结束元素
inline void add(int l,int r,const int val) {
int size=b.blo_size;
int st=l/size,end=r/size;
if(st==end) {
b.dat[st]+=(r-l+1)*val;
for(int i=l; i<=r; i++) a[i]+=val;
return;
}
//区间完全包含起始块
if(l==st_index(st)) {
b.dat[st]+=size*val;
b.addded[st]+=val;
}
//区间不完全包含起始块
else {
for(int i=l; i<=end_index(st); i++) {
a[i]+=val;
b.dat[st]+=val;
}
}
//注意区分块的编号(st,end)与区间编号(l,r)
for(int i=st+1; i<end; i++) {
b.dat[i]+=size*val;
b.addded[i]+=val;
}
if(r==end_index(end)) {
b.dat[end]+=size*val;
b.addded[end]+=val;
}
else {
for(int i=st_index(end); i<=r; i++) {
a[i]+=val;
b.dat[end]+=val;
}
}
}
inline long query(int l,int r) {
int size=b.blo_size;
int st=l/size,end=r/size;
long res=0;
if(st==end) {
for(int i=l; i<=r; i++) res+=a[i]+b.addded[st];
return res;
}
if(l==st_index(st)) res+=b.dat[st];
else {
for(int i=l; i<=end_index(st); i++) res+=a[i]+b.addded[st];
}
for(int i=st+1; i<end; i++) res+=b.dat[i];
if(r==end_index(end)) res+=b.dat[end];
else {
for(int i=st_index(end); i<=r; i++) res+=a[i]+b.addded[end];
}
return res;
}
int main() {
int m;
scanf("%d%d",&n,&m);
b.blo_size=(int)sqrt(n),b.num_blo=n/b.blo_size;
for(int i=0; i<n; i++) {
int x;
scanf("%d",&x);
add(i,i,x);
}
while(m--) {
short op;
int l,r;
scanf("%hd%d%d",&op,&l,&r);
if(op==2) printf("%lld\n",query(l-1,r-1));
else {
int k;
scanf("%d",&k);
add(l-1,r-1,k);
}
}
return 0;
}
注:对应原题地址:洛谷P3372 【模板】线段树 1。本来这题是用线段树做的,但还没学,就先用分块,稍后学了线段树就再更一篇线段树的。