学习记录 分块思想

有时对于一些区间操作,比如求区间的最大(小)值、和、乘积等,如果朴素地求解,每次操作需要 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。本来这题是用线段树做的,但还没学,就先用分块,稍后学了线段树就再更一篇线段树的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值