分块学习笔记

分块


通过适当的划分预处理并且保存下来,用空间换取时间,达到空间平衡。和线段树/树状数组相比更加接近朴素做法,但是更加通用、直观、容易实现(抄自算阶)。

模板(数列分块入门1)

分块长度为\(\sqrt{n}\),大块打标记,小块直接修改,查询值为标记 \(+\) 原值。

预处理

	for(int i=1;i<=s;i++)lf[i]=rt[i-1]+1,rt[i]=i*s;
	if(rt[s]<n)s++,lf[s]=rt[s-1]+1,rt[s]=n;
	for(int i=1;i<=s;i++)
		for(int j=lf[i];j<=rt[i];j++)pos[j]=i;

查询&修改

long long query(int x)
{
	return a[x]+tg[pos[x]];
}
void upd(int l,int r,long long k)
{
	if(pos[l]==pos[r])
	{
		for(int i=l;i<=r;i++)a[i]+=k;
	}
	else
	{
		for(int i=l;i<=rt[pos[l]];i++)a[i]+=k;
		for(int i=pos[l]+1;i<=pos[r]-1;i++)tg[i]+=k;
		for(int i=lf[pos[r]];i<=r;i++)a[i]+=k;
	}
}

树上分块

王室联邦

dfs时维护一个栈,离开每个点时压栈,自下向上合并,如果某棵子树深搜后栈内元素数量 \(\ge B\)就把栈内元素放在一个块里。但是
如果某棵子树深搜之后大小达不到 \(B\) ,但是在下一棵子树更靠下的位置超过了 \(B\) ,就会导致分块不联通,所以每次进入递归时还需要维护一个弹栈上限。

int top=0,st[N];//维护已经完成遍历的点 
int cnt=0,rt[N],pos[N];//每个省的省会和各点在哪个省中 
void dfs(int x,int fa=-1)
{
	int las=top;//弹栈上限 
	for(int i=lk[x];i;i=e[i].nxt)
	{
		int y=e[i].y;
		if(y==fa)continue;
		dfs(y,x);
		if(top-las>=b)//子树可以成为一个省 
		{
			rt[++cnt]=x; 
			while(top>las)pos[st[top--]]=cnt;
		}
	}
	st[++top]=x;//压栈 
}

题目

数列分块入门2
预处理把每个大块放进vector里排一下序,修改同上,查询时lower_bound一下。数列分块入门3
同上,查询时返回下标对应值。

数列分块入门4
预处理每块的和,修改时大块标记小块暴力,查询时大块直接加小块暴力。

数列分块入门5
看起来很像势能线段树那题,所以可以用相似的做法。如果一个块内全部为 \(0/1\) ,打上标记,以后修改到此处就直接跳过了。

数列分块入门6
vector维护块内元素,直接插入,查询时大块跳过,小块暴力。因为数据是随机生成的,所以所有的插入操作不会让原先分的块变得太丑陋。但是如果不是随机数据的话就会破坏我们的复杂度,因此考虑重新分块。

数列分块入门7
线段树2就很像。但是更新标记的时候只能直接把大块标记传递到单点上。

数列分块入门8
似乎无法预处理,但是注意到每次修改之后会有一片相同的值,所以小块暴力修改大块打标记。注意如果破坏大块修改小块需要传递并清空标记。

作诗
发现区间内元素出现次数很难用线段树维护,考虑分块。大块预处理答案和前缀和,小块暴力。


易错

  1. 注意块长算出 \(0\) 的情况。
  2. 不要忘记pos数组赋初值。(?
posted @ 2025-05-16 21:17  baiguifan  阅读(17)  评论(0)    收藏  举报