学习笔记:分块

前言

非常粗略

概念

什么是分块算法?
很简单 就是暴力 把一段长度为 \(n\) 的序列 分成 \(\sqrt{n}\) 块 块长为 \(\sqrt n\) 然后进行一系列暴力乱搞
它的好处就是非常暴力 好!
先来看一道 板子

题目要求我们区间加一个数 区间查询一段和
这个东西怎么搞?
考虑分块!
首先呢 把原数列分为了 \(\sqrt n\) 块 可能末尾还会多一块不足 \(\sqrt n\) 长度的块
然后 我们需要预处理以下的值:

  • \(bel_x\) 查询每个数在哪个块
  • \(L_x\) 这个块的左端点
  • \(R_x\) 这个块的右端点
  • \(block\) 块长
  • \(tot\) 块数
    注意处理最后一块的边界和块数即可

易得每一块的左端点为 \((i-1)\times block +1\) 右端点为 \(i\times block\)
根据这样算一下即可

void build()
{
	block=sqrt(n);
	tot=n/block;
	if(n%block) tot++;
	for(int i=1;i<=tot;i++)
		L[i]=R[i-1]+1,R[i]=i*block;
	R[tot]=n;
	for(int i=1;i<=n;i++)
		bel[i]=(i-1)/block+1,sum[bel[i]]+=a[i];
}

预处理完毕 思考怎么修改/求中间一段
其实很简单

  • 对于中间的整段 借用线段树的思想 打个 \(tag\) 走路即可 不超过 \(O(\sqrt n)\)
  • 对于左右的零散段 暴力改即可 不超过 \(O(\sqrt n)\)

时间复杂度 \(O(\sqrt n)\)
注意特判在一个块内的即可

void updata(int l,int r,ll x)
{
	int ls=bel[l],rs=bel[r];
	if(ls==rs)
	{
		for(int i=l;i<=r;i++)
			a[i]+=x,sum[ls]+=x;
		return ;
	}
	for(int i=l;i<=R[ls];i++)
		a[i]+=x,sum[ls]+=x;
	for(int i=L[rs];i<=r;i++)
		a[i]+=x,sum[rs]+=x;
	for(int i=ls+1;i<=rs-1;i++)
		tag[i]+=x,sum[i]+=(R[i]-L[i]+1)*x;
	return ;
}

然后是查询
借用和修改一样的操作就行 时间复杂度 \(O(\sqrt n)\)

ll query(int l,int r)
{
	int ls=bel[l],rs=bel[r];
	ll ans=0;
	if(ls==rs)
	{
		for(int i=l;i<=r;i++)
			ans+=a[i]+tag[ls];
		return ans;
	}
	for(int i=l;i<=R[ls];i++)
		ans+=a[i]+tag[ls];
	for(int i=L[rs];i<=r;i++)
		ans+=a[i]+tag[rs];
	for(int i=ls+1;i<=rs-1;i++)
		ans+=sum[i];
	return ans;
}

这样 我们优雅的暴力程序就出来了 时间复杂度 \(O(n\sqrt n)\) 还是很不错的

posted @ 2023-08-28 15:54  g1ove  阅读(12)  评论(0编辑  收藏  举报