[分块] 分块入门1~4

Posted zero_orez6

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[分块] 分块入门1~4相关的知识,希望对你有一定的参考价值。

何为分块

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

  • 树状数组 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;
}


以上是关于[分块] 分块入门1~4的主要内容,如果未能解决你的问题,请参考以下文章

数列分块入门9 解题报告

数列分块入门3 解题报告

数列分块入门6 解题报告

数列分块入门8 解题报告

数列分块入门5 解题报告

[分块] 分块入门1~4