Gokix

一言(ヒトコト)

关注我

简单分块 学习笔记

$$····案外寂しいものだな····$$

- ACW243 一个简单的整数问题2

题意:区间加区间和

以此为例讲解块状数组基本的操作。

声明变量、标记:

数组长度 \(n\),原数组 \(a\),块长 \(bl\),块数 \(tot\),第 \(i\) 个块的左右端点 \(L_i,R_i\),区间和标记 \(sum\),区间加懒标记 \(tag\),第 \(i\) 个数所属块的下标 \(col_i\)

1. 初始化

变量:

一般块长取 \(\sqrt n\)

所以块数就是 \(\lceil \frac{n}{bl} \rceil\)

块的左右端点观察一下就发现有式子:\(L_i=(i-1) \times bl+1,R_i=i \times bl\)。特别地,对于最后一个块(不管整还是零散),都有 \(R_{tot}=n\)

每个点所属的块也是易得的:\(col_i=(i-1)/bl+1\)

标记:

只需要把 \(sum\) 对应的 \(a\) 值加起来就行了。

\(tag\) 一开始自然是 0.

void init(){
	long long i;
	bl=sqrt(n);
	tot=n/bl;
	if(n%bl) tot++;
	for(i=1;i<=tot;i++){
		L[i]=(i-1)*bl+1;
		R[i]=i*bl;
	}
	R[tot]=n;
	for(i=1;i<=n;i++) col[i]=(i-1)/bl+1;
	for(i=1;i<=n;i++) sum[col[i]]+=a[i];
}

2.区间加

假设对 \([x,y]\) 区间加 \(w\)

首先特判 \(x\)\(y\) 在同一个块里,视作一个散块暴力处理即可。

否则对左右两侧的零散块暴力处理,对中间的整块更改其标记。

void upd(long long x,long long y,long long w){
	long long i;
	if(col[x]==col[y]){
		for(i=x;i<=y;i++){
			a[i]+=w;sum[col[x]]+=w;
		}
		return;
	}
	for(i=x;i<=R[col[x]];i++){
		a[i]+=w;sum[col[x]]+=w;
	}
	for(i=L[col[y]];i<=y;i++){
		a[i]+=w;sum[col[y]]+=w;
	}
	for(i=col[x]+1;i<=col[y]-1;i++){
		tag[i]+=w;sum[i]+=bl*w;//tag针对于零散块的求和(在算零散块的时候有用),sum针对于整块的求和,所以这里都得改
	}
}

3.区间和

假设对 \([x,y]\) 求区间和。

同理,还是要先特判 \(x\)\(y\) 在同一个块里,暴力加起来即可。加的是 \(a\)\(tag\)

否则,散块暴力(加的是 \(a\)\(tag\)),整块直接加 \(sum\)

long long query(long long x,long long y){
	long long i,res=0;
	if(col[x]==col[y]){
		for(i=x;i<=y;i++){
			res+=a[i]+tag[col[x]];
		}
		return res;
	}
	for(i=x;i<=R[col[x]];i++){
		res+=a[i]+tag[col[x]];
	}
	for(i=L[col[y]];i<=y;i++){
		res+=a[i]+tag[col[y]];
	}
	for(i=col[x]+1;i<=col[y]-1;i++){
		res+=sum[i];
	}
	return res;
}

至此我们就解决了本题:code


- P2801 教主的魔法

题意:区间加区间rank

以此为例说明块内二分。

区间加和上例基本相同,还是打标记。转化是查一段区间改完后有多少数 \(\ge k\) 就等于查原来这些数有多少 \(\ge k-addtag\).

考虑区间rank:

我们复制原数组,并将其按块从小到大排序,记作 \(b\)

这样,我们就可以在整块里根据 \(b\) 进行二分,而散块直接暴力完事。

code

image

posted @ 2022-05-28 11:18  Gokix  阅读(26)  评论(0编辑  收藏  举报