[分块] 分块入门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 (i−1)⌊N⌋+1,右端点为 m i n ( i ∗ ⌊ N ⌋ , N ) min(i*\\lfloor \\sqrt{N} \\rfloor,N) min(i∗⌊N⌋,N)
如下图所示:
剩下的就是根据题目要求在不同的段上进行操作
时间复杂度
因为段数以及段长都是 O ( N ) O(\\sqrt N) O(N)所以整个算法的时间复杂度约为 O ( ( N + M ) ∗ N ) O((N+M)*\\sqrt N) O((N+M)∗N)。
例题
按照上面的方法将序列分块,在每次执行加操作时用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数组中的值。
当然你也可以写一个区间加,区间求和一下水两道题
老样子对序列进行分块,预处理将每一块进行排序,在查询是二分查找
于第二题相同,将二分改为查询小于某个数的最大值即可。
出现了,野生的区间加!
预处理出数组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]+=(R−r[i−1])∗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的主要内容,如果未能解决你的问题,请参考以下文章