[分块] 分块入门1~4

分块算法是一种优化暴力算法的策略,通过预先处理和保存信息来平衡时间和空间。它将序列分成若干段,简化操作并降低复杂度。基本步骤包括按特定方式划分序列,对每个块进行处理,并在需要时进行区间操作。常见应用包括区间加操作、排序查询和寻找特定值。分块算法相比线段树和树状数组,具有更简单的思路和易于实现的代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

何为分块

在学习完线段树和树状数组后,这两种数据结构已经能够解决大部分的问题了,但在使用过程中不难发现他们的缺点

  • 树状数组 O ( ( N + M ) l o g N ) O((N+M)logN) O((N+M)logN):局限性过大,不易扩展到普遍性题目
  • 线段树 O ( ( N + M ) l o g N ) O((N+M)logN) O((N+M)logN):虽然能够扩展到许多题目,其也有许多类型,但不够直观,并且代码长,细节多…

分块应运而生

分块的基本思想是通过适当的划分,预处理一部分信息并保存下来,用空间换取时间,达到"时空平衡" ———《算法竞赛》

说得通俗一点,就是依据某种方法优化的暴力算法,相比线段树和树状数组,分块思路更简单,且更通用,代码容易实现。

分块的基本过程

分块,分块,顾名思义,是将一个序列分成若干段来进行操作。

通常将一个序列A分成若干个不超过 ⌊ N ⌋ \lfloor \sqrt{N} \rfloor N 的段,其中第i段的左端点为 ( i − 1 ) ⌊ N ⌋ + 1 (i-1)\lfloor \sqrt{N} \rfloor +1 (i1)N +1,右端点为 m i n ( i ∗ ⌊ N ⌋ , N ) min(i*\lfloor \sqrt{N} \rfloor,N) min(iN ,N)

如下图所示:在这里插入图片描述

剩下的就是根据题目要求在不同的段上进行操作

时间复杂度

因为段数以及段长都是 O ( N ) O(\sqrt N) O(N )所以整个算法的时间复杂度约为 O ( ( N + M ) ∗ N ) O((N+M)*\sqrt N) O((N+M)N )

例题

分块入门1

按照上面的方法将序列分块,在每次执行加操作时用add表示某一段的“增量标记”,也就是这一段整体所加的值。

对于某一个区间加操作(l,r,c):

  • 若l,r在同一区间内,则直接令 a [ l ] a[l] a[l]~ a [ r ] a[r] a[r]全部加上c.
  • 若他们不在同一区间:则令l~r之间的整块i,add[i]加上c,那么开头和结尾不为整块的部分像第一步一样直接加。

最后在询问某个数的值时,注意加上add数组中的值。

当然你也可以写一个区间加,区间求和一下水两道题

分块入门2

老样子对序列进行分块,预处理将每一块进行排序,在查询是二分查找

分块入门3

于第二题相同,将二分改为查询小于某个数的最大值即可。

分块入门4

出现了,野生的区间加!

预处理出数组sum,记录某一段i的区间和sum[i],以及增量数组add,add数组的操作如题1,下面详细讲解sum数组的具体操作:

  • 对于某一整段i, s u m [ i ] + = ( r [ i ] − l [ i ] + 1 ) ∗ c sum[i]+=(r[i]-l[i]+1)*c sum[i]+=(r[i]l[i]+1)c
  • 若不是一个整段,则 s u m [ i ] + = ( l [ i + 1 ] − L ) ∗ c sum[i]+=(l[i+1]-L)*c sum[i]+=(l[i+1]L)c s u m [ i ] + = ( R − r [ i − 1 ] ) ∗ c sum[i]+=(R-r[i-1])*c sum[i]+=(Rr[i1])c

最后答案为

  • 若在一整段内 a n s = s u m [ i ] + a d d [ i ] ∗ ( r [ i ] − l [ i ] ) ans=sum[i]+add[i]*(r[i]-l[i]) ans=sum[i]+add[i](r[i]l[i])
  • 否则用暴力直接算出不满整段的两部分(最多为 N \sqrt N N )。

code

//分块入门 4
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=50086;
int n,a[N],l[N],r[N],pos[N];
LL sum[N],add[N];
void change(int ll,int rr,LL d)
{
	int p=pos[ll],q=pos[rr];
	if(p==q)
	{
		for(int i=ll;i<=rr;i++)
		{
			a[i]+=d;
		}
		sum[p]+=d*(rr-ll+1);
	}
	else {
		for(int i=p+1;i<=q-1;i++) add[i]+=d;
		for(int i=ll;i<=r[p];i++) a[i]+=d;
		sum[p]+=d*(r[p]-ll+1);
		for(int i=l[q];i<=rr;i++) a[i]+=d;
		sum[q]+=d*(rr-l[q]+1);
	}
}
LL ask(int ll,int rr)
{
	int p=pos[ll],q=pos[rr];
	LL ans=0;
	if(p==q)
	{
		for(int i=ll;i<=rr;i++) ans+=a[i];
		ans+=add[p]*(rr-ll+1);
	}
	else {
		for(int i=p+1;i<=q-1;i++)
		{
			ans+=sum[i]+add[i]*(r[i]-l[i]+1);
		}
		for(int i=ll;i<=r[p];i++) ans+=a[i];
		ans+=add[p]*(r[p]-ll+1);
		for(int i=l[q];i<=rr;i++) ans+=a[i];
		ans+=add[q]*(rr-l[q]+1);
	}
	return ans;
}
int main()
{
 	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}	
	int t=sqrt(n);
	for(int i=1;i<=t;i++)
	{
		l[i]=(i-1)*t+1;
		r[i]=i*t;
	}
	if(r[t]<n) t++,l[t]=r[t-1]+1,r[t]=n;
	for(int i=1;i<=t;i++)
	{
		for(int j=l[i];j<=r[i];j++)
		{
			pos[j]=i;
			sum[i]+=a[j];
		}
	} 
	int m=n;
	while(m--)
	{
		int pd,ll,rr,c;
		cin>>pd>>ll>>rr>>c;
		if(pd==1)
		{
			cout<<ask(ll,rr)%(c+1)<<endl;
		}
		else 
		{
			change(ll,rr,c);
		}
	}
	return 0;
}


莫队相关

关于对询问的排序

bool cmp(node a,node b)
{
	int xd = (a.l - 1)/kuai + 1, yd = (b.l - 1) / kuai + 1;
	if(xd ^ yd) return xd<yd;
	if(xd & 1) return a.r < b.r;
	return a.r > b.r;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值