【学习笔记】分块

蒟蒻最近几天搞了一下分块算法,有一些感受,就在这里一起说了。


首先就是十分经典的分块与传统数据结构的对比:
(1)传统数据结构的复杂度:\(O(log_{2}n)\) 而分块复杂度:\(O(\sqrt n)\)
(2)传统数据结构维护的区间要求维护的信息可以高效合并,分块可以维护不可以高效合并的信息


分块因为不像数据结构可以非常模板化的一个东西,虽然也比较模板,但是格式上也比较随意,下面也就来稍微说说分块的思想
分块就是按照一定的大小(一般是 \(\sqrt n\)),将原序列分成一个个块,然后以块为单位处理与我们维护的信息有关的部分信息,然后对于查询操作就是将这个区间里整块的信息,也就是我们之前处理过的东西直接拿来用,然后没有处理过的,也就是这个区间的边角部分暴力进行求解。
考虑复杂度的证明:因为我们最多有 \(\sqrt n\) 个块,对于每一个我们询问的区间,其边角的部分,也就是不属于整块的部分最多 \(2 \times \sqrt n\) 个,所以我们询问一次的复杂度最多也就是 \(3 \times \sqrt n\) (当然不可能是这个数,肯定会小),我们的复杂度也是 \(O(\sqrt n)\)


相信大家肯定看的不是很明白,不大会实现,其实分块就跟暴力是差不多的,那么下面就通过一道例题来具体看看分块的写法

基本思路:
对于查询操作:每一个块都维护另一个数组,这个数组是一个排好序的数组,就是这个块内元素按顺序排列,对于整块直接二分查找有多少数大于等于 \(C\),这样我们最多 \(\sqrt n\) 个块,每个块二分的复杂度为 \(\log_{2}n\) ,剩下的不属于整块的部分暴力找就好,所以单次查询的复杂度为 \(O(\sqrt n · \log_{2}n)\)
对于修改操作:每个整块我们都采取打标记的方式,表示这一个整块被加了多少,然后对于边角的部分我们就暴力加,注意暴力加完了之后还需要维护好我们的排序后的数组,这样单次修改的复杂度就是 \(O(\sqrt n)\)
所以整体 \(q\) 次查询的复杂度就是:\(O(q·\sqrt n·\log_{2}n)\)
看思路肯定是打不出代码的,下面来放一下代码:




方便直接复制下面放文本版:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6+5;
int n,q,S,tot,bl[MAXN],br[MAXN],sa[MAXN],a[MAXN],tag[MAXN];
void sort_block(int x){
	for(int i=bl[x]; i<=br[x]; i++)
		sa[i] = a[i];
	sort(sa+bl[x],sa+br[x]+1);
}
void prework(){
	for(int i=1; i<=n; i++){
		if(i % S == 1){
			br[tot] = i-1;
			bl[++tot] = i;
		}
	}
	br[tot] = n;
	br[tot+1] = bl[tot+1] = n+1;
	for(int i=1; i<=tot; i++)
		sort_block(i);
}
void add_val(int l,int r,int val){
	int L = (l-1) / S + 1,R = (r-1) / S + 1;
	if(r - l + 1 <= 2 * S){
		for(int i=l; i<=r; i++){
			a[i]+=val;
		}
		for(int i=L; i<=R; i++)
			sort_block(i);
	}
	else{
		bool tagl = true,tagr = true;
		if(l == bl[L]){
			tagl = false;
			L--;
		}
		if(r == br[R]){
			tagr = false;
			R++;
		}
		for(int i = L+1; i<=R-1; i++){
			tag[i]+=val;
		}
		for(int i=l; i<=br[L]; i++){
			a[i]+=val;
		}
		if(tagl)	sort_block(L);
		for(int i=bl[R]; i<=R; i++){
			a[i]+=val;
		}
		if(tagr)	sort_block(R);
	}
}
int find(int x,int val){
	int l = bl[x],r = br[x],ans = br[x] + 1,mid = (l+r)>>1;
	while(l <= r){
		mid = (l+r)>>1;
		if(sa[mid] >= val){
			ans = mid;
			r = mid-1;
		}
		else{
			l = mid+1;
		}
	}
	return br[x] - ans + 1;
}
int query(int l,int r,int val){
	int L = (l - 1) / S + 1,R = (r - 1) / S + 1,ans = 0;
	if(r - l + 1 <= 2 * S){
		for(int i=l; i<=r; i++){
			if(a[i] + tag[(i-1) / S + 1] >= val)
				ans++;
		}
		return ans;
	}
	else{
		if(l == bl[L])	L--;
		if(r == br[R])	R++;
		for(int i=L+1; i<=R-1; i++){
			ans += find(i,val - tag[i]);
		}
		for(int i=l; i<=br[L]; i++){
			if(a[i] + tag[L] >= val)
				ans++;
		}
		for(int i = bl[R]; i<=r; i++){
			if(a[i] + tag[R] >= val){
				ans++;
			}
		}
		return ans;
		
	}
}
int main(){
	cin>>n>>q;
	for(int i=1; i<=n; i++){
		cin>>a[i];
	}
	S = sqrt(n);
	prework();
	while(q--){
		char opt;
		int l,r,x;
		cin>>opt>>l>>r>>x;
		if(opt == 'M'){
			add_val(l,r,x);
		}
		else if(opt == 'A'){
			printf("%d\n",query(l,r,x));
		}
	}
	return 0;
} 

其实分块的代码也不用我多说,咱们仔细地看一看也就能看懂了,因为分块的思路就相当于暴力

posted @ 2022-04-04 20:40  linyihdfj  阅读(47)  评论(0编辑  收藏  举报