前言
分块可以解决几乎全部的区间查询区间更新等问题,功能比线段树和树状数组要强大,但是时间复杂度会更大一点。其实分块就是一种优化过的暴力,它是对于整体进行像线段树一样的维护,对局部进行暴力的修改。
原理
顾名思义分块分块,我们把长度为n的序列分为若干块。维护块内信息即可。
又要问了,要多大呢?通常将块的大小设为 n \sqrt n n ,用pos数组储存每个块的位置。
最后分完有剩,那么剩余的单独为一个块。
我们用线段树的例题来介绍传送门luoguP3372
题目要求:
1.区间更新:
我们找在这个区间有多少个块是全部在里面的,因为这些块全要更新,我们可以添加懒惰标记,下次要用时才更新。
对于部分在区间的块,我们只有暴力的把这些在区间内的点更新即可、
2.区间查询:
还是讨论如果被区间包含的块,就直接加上处理懒标的值和之前的和即可。
部分在区间的块,就暴力计算值即可。
代码非常简单。也好理解。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
int n,m,a[N];
int pos[N],sum[N],L[N],R[N],lz[N];
//块的位置,块的和,块的左区间和右区间,懒惰标记
inline void build()
{
int t=sqrt(n*1.0);
int num=n/t;
if(n%t) num++;//如果有剩余,再加一个块
for(int i=1;i<=num;i++)
{
L[i]=(i-1)*t+1;//每个块的范围
R[i]=i*t;
}
R[num]=n;//最后一个块的右区间是n
for(int i=1;i<=num;i++)
for(int j=L[i];j<=R[i];j++)
{
pos[j]=i;//该点属于哪个块
sum[i]+=a[j];//统计和
}
}
inline void update(int l,int r,int k)
{
int x=pos[l],y=pos[r];
if(x==y)//块包含了这个区间,暴力
{
for(int i=l;i<=r;i++)
a[i]+=k;
sum[x]+=k*(r-l+1);
}
else
{
for(int i=x+1;i<=y-1;i++)//全部在区间的,懒标
lz[i]+=k;
for(int i=l;i<=R[x];i++)//部分在区间的,暴力
a[i]+=k;
sum[x]+=k*(R[x]-l+1);
for(int i=L[y];i<=r;i++)
a[i]+=k;
sum[y]+=k*(r-L[y]+1);
}
}
inline int query(int l,int r)
{
int x=pos[l],y=pos[r];//得到所在块的位置
int ans=0;
if(x==y)//块包含了这个区间,暴力
{
for(int i=l;i<=r;i++)
ans+=a[i];
ans+=lz[x]*(r-l+1);
}
else
{
for(int i=x+1;i<=y-1;i++)//处理懒标
ans+=sum[i]+lz[i]*(R[i]-L[i]+1);
for(int i=l;i<=R[x];i++)//暴力
ans+=a[i];
ans+=lz[x]*(R[x]-l+1);
for(int i=L[y];i<=r;i++)
ans+=a[i];
ans+=lz[y]*(r-L[y]+1);
}
return ans;
}
signed main()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
build();//预处理
int cas,x,y,k;
for(int i=1;i<=m;i++)
{
scanf("%lld",&cas);
if(cas==1)
{
scanf("%lld%lld%lld",&x,&y,&k);
update(x,y,k);
}
else
{
scanf("%lld%lld",&x,&y);
cout<<query(x,y)<<endl;
}
}
return 0;
}
再来一道分块题目传送门HDU4417
题目要求我们求出区间内小于等于h的数的个数。
首先分块。
我们不能把块排序因为会改变位置,但我们可以复制一份,将新的数组tmp排序,对于每一块。
对于被包含在区间的块,只需在tmp中找到临界的点,它以下的点必定都小于等于h,可以使用upper_bound找到第一个大于h的数,那么它以下的数就一定小于等于h了。
对于部分在区间的块,我们就只能枚举这些数,是否小于等于h,因为排序是对整体进行排序,局部是无法处理的。
代码实现:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int L[N],R[N],pos[N],tmp[N],a[N];
int n,m,t;
inline void build()
{
int t=sqrt(n*1.0);
int num=n/t;
if(n%t) num++;
for(int i=1;i<=num;i++)
L[i]=(i-1)*t+1,R[i]=i*t;
R[num]=n;
for(int i=1;i<=num;i++)
{
for(int j=L[i];j<=R[i];j++)
pos[j]=i;
sort(tmp+L[i],tmp+R[i]+1);
}
}
inline int query(int l,int r,int h)
{
int ans=0;
if(pos[l]==pos[r])
{
for(int i=l;i<=r;i++)
if(a[i]<=h) ans++;
}
else
{
for(int i=l;i<=R[pos[l]];i++)
if(a[i]<=h) ans++;
for(int i=L[pos[r]];i<=r;i++)
if(a[i]<=h) ans++;
for(int i=pos[l]+1;i<=pos[r]-1;i++)
ans+=upper_bound(tmp+L[i],tmp+R[i]+1,h)-tmp-L[i];
}
return ans;
}
int main()
{
scanf("%d",&t);
for(int o=t;o<=t;o++)
{
printf("Case %d:\n",o);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),tmp[i]=a[i];
build();
int x,y,h;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&h);
cout<<query(x+1,y+1,h)<<endl;
}
}
return 0;
}