【学习笔记】分块

upd on 2022.11.13 16:59

分块是一种比较朴素的数据结构。
但我认为它是相对比较灵活的。

核心思想就是对数组进行分块
对于每一块维护相应的信息

常见的分块思想都可以用“大段维护,局部朴素”来形容(出自李煜东老师的《算法竞赛进阶指南》)


预处理:
1.确定块长,这里有一个技巧,也是一个重点,就是可以调整块长
2.确定每一块的区间左端点右端点),每个点所在的块编号

修改:
1.在同一块里,暴力处理
2.不在同一块中,把 左端点所在块右端点所在块 单独暴力处理
中间的各个块,每一块打上tag

查询:
1.在同一块里,暴力查询
2.不在同一块中,把 左端点所在块右端点所在块 单独暴力查询
中间的各个块,把答案加上每一块块整体的信息


非常朴素,时间复杂度为 \(O((N+Q)\sqrt{N} )\) ,是为数不多的带着根号的做法
\(2\times 10^{5}\) 的数据范围内能轻松跑过,
同时又常数小的优点,对于我这种大常数选手来说,完全比线段树好多了!
而且好写通用,可以处理非区间可加性问题(比如求众数)
所以实在不行,在\(n\)比较大的时候(只能使用\(O(n\log_{2}{n})\)做法),也可以用分块骗暴力分,甚至怒草\(AC\)


然后上代码。
洛谷 P3372 【模板】线段树 1为例,做区间修改区间查询

一、预处理

1.确定块长,想调整也可以调整,因为针对不同数据,不同的块长的效率不一样。块长太长了和暴力没什么区别,太短了也和暴力没什么区别,通常取\(\left \lfloor \sqrt{n} \right \rfloor\)
2.确定每一块的区间左端点右端点),每个点所在的块编号

\(d\)为块长,\(dn\)为块的数量

	d=(int)sqrt(n);
	if(n%d==0)
		dn=n/d;
	else
		dn=n/d+1;

这里要判断整除的。因为不能整除的话后面会有一小段。

void pre(){
	
	for(int i=1;i<=n;i++){
		sum[i]=sum[i-1]+a[i];
	}
	
	for(int i=1;i<=n;i++){
		if((i-1)%d==0)
			pos[i]=pos[i-1]+1;
		else
			pos[i]=pos[i-1];
	}
	
	for(int i=1;i<=dn;i++){
		L[i]=(i-1)*d+1;
		R[i]=min(n,i*d);
		s[i]=sum[R[i]]-sum[L[i]-1];
	}
	
	return;
}

这里是先对每一块设置基本信息,本题中的信息是区间和,是先用前缀和预处理了一下的。

二、修改:

1.在同一块里,暴力扫过去,每个单点加
2.不在同一块中,把 左端点所在块右端点所在块 单独暴力扫,单点
中间的各个块,每一块的tag加,tag在本题中的含义是快被整体加了多少

void upd(int l,int r,int v){
	int pl=pos[l],pr=pos[r];
	
	if(pl==pr){
		for(int i=l;i<=r;i++)
			a[i]+=v;
		s[pl]+=v*(r-l+1);
		return;
	}
	for(int i=pl+1;i<=pr-1;i++)
		tag[i]+=v;
	
	for(int i=l;i<=R[pl];i++)
		a[i]+=v;
	s[pl]+=v*(R[pl]-l+1);
	
	for(int i=L[pr];i<=r;i++)
		a[i]+=v;
	s[pr]+=v*(r-L[pr]+1);
	return;
}

这里用\(pl\)\(pr\)代表左右端点所在区块的编号
还是比较好理解的

三、查询:

1.在同一块里,暴力查询
2.不在同一块中,把 左端点所在块右端点所在块 单独暴力查询
中间的各个块,把答案加上每一块块整体的信息

int query(int l,int r){
	int pl=pos[l],pr=pos[r];
	int ans=0;
	
	if(pl==pr){
		for(int i=l;i<=r;i++)
			ans+=a[i];
		ans+=tag[pl]*(r-l+1);
		return ans;
	}
	
	for(int i=pl+1;i<=pr-1;i++)
		ans+=s[i]+tag[i]*(R[i]-L[i]+1);
		
	for(int i=l;i<=R[pl];i++)
		ans+=a[i];
	ans+=tag[pl]*(R[pl]-l+1);
	
	for(int i=L[pr];i<=r;i++)
		ans+=a[i];
	ans+=tag[pr]*(r-L[pr]+1);
	
	return ans;
}

基本上和修改差不多,应该是不用讲了。


然后就完了。很好写也很快,就\(AC\)了。

所以分块\(yyds\)

2022.11.13

posted @ 2022-11-13 11:00  Aslf_Ek  阅读(108)  评论(2)    收藏  举报