一.分块的基本知识
分块的原理
1.什么是分块?:事实上就是一种优雅的暴力,将 O ( n ) O(n) O(n) 的暴力过程变成 O ( n 1 / 2 ) O(n^{1/2}) O(n1/2) 的过程。用于区间问题。
2.分块可以干嘛
- 分块可以实现的操作有:(每个操作的时间复杂度都为O(n1/2)
- 区间修改: [ l , r ] + k [l,r]+k [l,r]+k
- 区间查询和: ∑ i = l r a i \sum_{i=l}^r a_i ∑i=lrai
- 区间查询最值: m a x ( a [ i ] ) , i ∈ [ l , r ] max(a[i]),i\in[l,r] max(a[i]),i∈[l,r]
- 区间查询 [ l , r ] [l,r] [l,r] 内有多少个数满足: > k , < k , = k >k,<k,=k >k,<k,=k
- 区间查询 [ l , r ] [l,r] [l,r] 内,k 的前驱后继
- 【注意】要知道,线段树实现第四点和第五点是很难的,而树套又过于麻烦了
3.怎么分块?
- 将每 n 1 / 2 n^{1/2} n1/2 个数存储到一个块,一共分出 n 1 / 2 n^{1/2} n1/2 个块,实际上就是一个纯模拟的过程。
- 预处理: 计算出每个块的大小 l e n len len,块的数量 p o s [ n ] pos[n] pos[n],每个块的左右区间边界 l b , r b lb,rb lb,rb,每个元素所属块的编号 p o s pos pos,对每个块进行初始化(因题而定)
- 区间修改与查询: 区间 [ l , r ] [l,r] [l,r] 中整块的部分直接处理,非整块的部分暴力处理。(有时候也会用到懒标记的思想)
4.代码实现(区间修改,区间求和)*(O(n3/2))
分块模板
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10,M=1e4+10;
int n,len;
int sum[M],a[N];
int lb[M],rb[M];//各个块的区间左右边界
int pos[N];//每个元素的所属块的编号
int tag[M];//各个块的区间懒标记(可能会用到)
void init(int n) {//分块预处理
len=sqrt(n);//块的大小
for(int i=1;i<=n;i++)pos[i]=(i-1)/len+1;//每个元素所属块的编号
for(int i=1;i<=pos[n];i++){//每个块的左右边界
lb[i]=(i-1)*len+1;
rb[i]=min(i*len,n);
}
//对每个块进行初始化,下面以预处理块和为例
for(int i=1; i<=pos[n]; i++) {
for(int j=lb[i]; j<=rb[i]; j++) {
sum[i]+=a[j];
}
}
}
void Range_query(int l,int r) {//区间修改或查询
if(pos[l]==pos[r]) {//若l与r所属块在同一块,暴力修改
for(int i=l; i<=r; i++){
}
return;
}
for(int i=l; i<=rb[pos[l]]; i++) {//左边那不完整的块,暴力
}
for(int i=lb[pos[r]]; i<=r; i++) {//右边那不完整的块,暴力
}
for(int i=pos[l]+1; i<=pos[r]-1; i++) {//中间完整的块,快速求值
}
}
例题1:区间修改+区间查询
例题2:求区间 [ l , r ] [l,r] [l,r] 中大于 k 的数的个数
方法:
1.用一个辅助数组b来对a数组的每一个块来进行排序
2.更新时,
对于区间[l,r]中完整的块,排序顺序不影响,mark即可
对于区间左右不完整的块,update完重排一次
3.查询时,
对于区间[l,r]中完整的块,对每一块lower_bound即可
对于区间左右不完整的块,暴力判即可
#include<bits/stdc++.h>
using namespace std;
int tot,len;
int L[10005],R[10005];//各个块的区间左右边界
int a[1000005];//每个元素的非真实值
int b[1000005];//辅助数组(排序数组a的每个块)
int belong[1000005];//每个元素的所属块的编号
int mark[10005];//各个块的区间懒标记(区间add标记)
void init(int n) {
len=sqrt(n);
tot=n/len;
if(n%len)tot++;
for(int i=1; i<=tot; i++) {
L[i]=(i-1)*len+1;
R[i]=i*len;
}
R[tot]=n;
for(int i=1; i<=n; i++)belong[i]=(i-1)/len+1;
//辅助数组对每个块a排序
for(int i=1; i<=tot; i++) {
for(int j=L[i]; j<=R[i]; j++) {
b[j]=a[j];
}
sort(b+L[i],b+R[i]+1);
}
}
//对第x块排序
void reset(int x) {
for(int i=L[belong[x]]; i<=R[belong[x]]; i++)b[i]=a[i];
sort(b+L[belong[x]],b+R[belong[x]]+1);
}
//查询第x块中,比>=k的数的个数
int find(int x,int k){
int t=lower_bound(b+L[x],b+R[x]+1,k)-b;
return R[x]-t+1;
}
//区间修改
void update(int l,int r,int k) {
if(belong[l]==belong[r]) {
for(int i=l; i<=r; i++)a[i]+=k;
reset(l);
return;
}
for(int i=l; i<=R[belong[l]]; i++)a[i]+=k;
reset(l);
for(int i=L[belong[r]]; i<=r; i++)a[i]+=k;
reset(r);
for(int i=belong[l]+1; i<=belong[r]-1; i++)mark[i]+=k;
}
//区间查询比k大的数有多少个
int query(int l,int r,int k) {
int ans=0;
//若l和r属于同一块,暴力判断
if(belong[l]==belong[r]) {
for(int i=l; i<=r; i++)if(a[i]+mark[belong[i]]>=k)ans++;
return ans;
}
//左边那不完整的块暴力判断
for(int i=l; i<=R[belong[l]]; i++)if(a[i]+mark[belong[i]]>=k)ans++;
//左边那不完整的块暴力判断
for(int i=L[belong[r]]; i<=r; i++)if(a[i]+mark[belong[i]]>=k)ans++;
//中间那些完整的块二分查找
for(int i=belong[l]+1; i<=belong[r]-1; i++) {
ans+=find(i,k-mark[i]);//求第i块中大于等于k的元素的个数
}
return ans;
}
int main() {
int n,q,l,r,k;
char op;
cin>>n>>q;
for(int i=1; i<=n; i++)cin>>a[i];
init(n);
for(int i=1; i<=q; i++) {
cin>>op>>l>>r>>k;
if(op=='M')update(l,r,k);
else cout<<query(l,r,k)<<endl;
}
return 0;
}