学莫队之前先看了看分块,,总结一下的话感觉还是一个暴力的算法
一、什么时候可以用到分块呢?
如果问,一个数列,给定m次询问,每次问一个区间内的和是多少?
很明显可以用前缀和来解决
那么如果问还要对随机一个区间进行加或减之类的修改呢?
很容易想到树状数组和线段树来解决
再问,如果在加上问一个区间大于等于k的数有几个呢?
这个时候就需要这个‘暴力’的方法出场了,我们可以用分块解决。
二、什么是分块?
一个有n个数的数列,将它平均分为sqrt(n)或sqrt(n)+1块,这里分成多少块取决于sqrt(n)是否是一个整数。
然后每次更新时对于一个整块可以直接进行更新,常用的是用一个atag标记数组标记,而对于那些不是一个整块
的范围,直接暴力枚举就可以了。
查询的时候也是这样,对于整块通常使用二分查询,不是整块的依然是暴力查询。
那么为什么要分成sqrt(n)块呢?
因为如果分的块数太多,累加块的时候就会变慢,而如果分的太少,每次对于一个块的累加就会变慢,综上,分成sqrt(n)
是最理想的分法。
分块的时间复杂度是O(sqrt(n)),为什么呢?
对于整个块的累加是O(sqrt(n)),而对于散的块累加,因为散的块的数不会超过sqrt(n),所以时间复杂度最多依然是sqrt(n)。
三、块的建立
因为查询和更新要因题而异
这里就说一下块的建立
void build()
{
blo=sqrt(n); //blo为每一块有多少个
for(int i=1;i<=n;i++){
b[i]=a[i]; //b数组用来排序原数组,以便最后二分查找时用
bl[i]=(i-1)/blo+1; //bl[i]表示第i个属于第几块
}
for(int i=1;i<=bl[n];i++){ //bl[n]表示的是最后一个元素在第几个块里,即一共分成了多少块
l[i]=(i-1)*blo+1; //l[i]表示第i块的左端点是第几个元素
r[i]=blo*i; //r[i]表示第i块的右端点是第几个元素
}
r[bl[n]]=n; //最后一个块的右端点必然是n
for(int i=1;i<=bl[n];i++)
sort(b+l[i],b+r[i]+1);
}
前面也提到了分块很“暴力”,,所以如果遇到大的数据范围就不要考虑它了......
这里给出一个例题洛谷p2801
如果还有对更新和查询操作有疑问的可以结合这个例题看一下
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1e6+10;
int a[N],b[N],l[N],r[N],bl[N],blo,atag[N],n,m; //bl[i]表示第i个属于第几块
void build()
{
blo=sqrt(n); //blo为每一块有多少个
for(int i=1;i<=n;i++){
b[i]=a[i]; //b数组用来排序原数组,以便最后二分查找时用
bl[i]=(i-1)/blo+1; //bl[i]表示第i个属于第几块
}
for(int i=1;i<=bl[n];i++){ //bl[n]表示的是最后一个元素在第几个块里,即一共分成了多少块
l[i]=(i-1)*blo+1; //l[i]表示第i块的左端点是第几个元素
r[i]=blo*i; //r[i]表示第i块的右端点是第几个元素
}
r[bl[n]]=n; //最后一个块的右端点必然是n
for(int i=1;i<=bl[n];i++)
sort(b+l[i],b+r[i]+1);
}
void reset(int x) //每次更新完重新排序
{
for(int i=l[x];i<=r[x];++i)
b[i]=a[i];
sort(b+l[x],b+r[x]+1);
}
void update(int x,int y,int z)
{
if(bl[x]==bl[y]){ //如果在一个块中,直接处理
for(int i=x;i<=y;++i)
a[i]+=z;
reset(bl[x]);
return ;
}
for(int i=x;i<=r[bl[x]];i++) //否则暴力处理不是整块的,就是x所在的那个块
a[i]+=z;
reset(bl[x]);
for(int i=l[bl[y]];i<=y;i++) //暴力处理不是整块的,也就是y所在的那个块
a[i]+=z;
reset(bl[y]);
for(int i=bl[x]+1;i<=bl[y]-1;i++) //其余的整块在处理,累加到atag数组中
atag[i]+=z;
}
int Find(int x,int z) //二分
{
int ll=l[x],rr=r[x];
while(ll<=rr){
int mid=(ll+rr)>>1;
if(b[mid]<z)
ll=mid+1;
else rr=mid-1;
}
return r[x]-ll+1;
}
int query(int x,int y,int z)
{
int ans=0;
if(bl[x]==bl[y]){ //如果在一个块中,直接查询
for(int i=x;i<=y;i++)
if(a[i]+atag[bl[i]]>=z)
ans++;
return ans;
}
for(int i=x;i<=r[bl[x]];i++) //否则依然是先处理散块
if(a[i]+atag[bl[i]]>=z)
ans++;
for(int i=l[bl[y]];i<=y;i++)
if(a[i]+atag[bl[i]]>=z)
ans++;
for(int i=bl[x]+1;i<=bl[y]-1;i++) //最后对整块处理
ans+=Find(i,z-atag[i]);
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
build();
int x,y,z;
char c;
for(int i=1;i<=m;i++){
cin>>c;
scanf("%d%d%d",&x,&y,&z);
if(c=='M')
update(x,y,z);
else printf("%d\n",query(x,y,z));
}
return 0;
}