分块[update,可以和原本的yxc板子对比]
分块
我发现,要学莫队,分块还是得学好。上一次写的分块的博客太简单,这一篇补一下。
分块是一种思想,把一个整体划分为若干个小块,对整块进行整体处理,对零散块进行单独处理。本文主要介绍块状数组,利用分块思想处理区间问题的一种数据结构。
块状数组将一个长度为 \(N\) 的数组划分为 \(\sqrt{N}\) 块,这样,对于任意的查询区间 \([l,r]\),我们可以将其拆分为最多不超过 \(\sqrt{N}\) 块和最多不超过 \(\sqrt{N}\) 个零散区间。总体复杂度为 \(O(\sqrt{N})\)。
显然,分块的时间复杂度比不上线段树和BIT这些对数级算法,但由此换来的,是更高的灵活性。与线段树不同,块状数组支持维护不满足结合律的信息,也不需要一层层传递标记。举一个例子,我们需要查询任意区间 \([l,r]\) 的重复次数不少于 \(3\) 的数字个数,如果用线段树维护,无法用一个时间复杂度少于为 \(O(N)\) 的函数 \(f\) 来求解 \(ans[l,r] = f(ans[l,mid], ans[mid+1,r])\) 。
因此我们需要引入块状数组。事实上,块状数组也可以看作一棵高度为 \(3\) 的树。
1. 预处理 \(O(N)\)
具体地,使用块状数组,我们需要划定每一个块所占据的范围、每个数的分块编号。
const int N = 1e6+5;
const int sqN = 1e3+5;
int n, sq;
int st[sqN]; // st[i] 表示i号块的第一个元素的下标
int ed[sqN]; // ed[i] 表示i号块的最后一个元素的下标
int id[N]; // id[i] 表示元素i的分块编号
// 维护信息
int arr[N]; // 零散数组
int sum[N]; // 分块和数组
int mark[N]; // 分块公共加数组
void init(){
sq = sqrt(n);
for(int i = 1; i <= n; ++i){
st[i] = n / sq * (i-1) + 1;
ed[i] = n / sq * i;
}
ed[sq] = n;
for(int i = 1; i <= sq; ++i){
for(int j = st[i]; j <= ed[i]; ++j){
id[j] = i;
}
}
}
2. 修改 \(O(\sqrt{N})\)
对于待修改区间 \([l, r]\),当 \(l\) 和 \(r\) 在同一块时,直接暴力修改零散数组和sum数组。如果不在同一块,则先处理 \([l, ed[id[l]]]\) 和 \([st[id[r]], r]\)这些零散点,然后再处理 \([id[l]+1, id[r]-1]\) 这些分块。
但是应该注意一点是:只有在修改零散点时去修改零散数组和sum数组。如果对于整个分块操作,应该只修改mark数组而不修改sum数组。这一点很重要,不要在操作整个分块的时候,令sum[i] += size[i] * x,否则就会重复计算
void modify(int l, int r, int x){
if(id[l] == id[r]){
for(int i = l; i <= r; ++i){
arr[i] += x;
}
sum[id[l]] += (r-l+1) * x;
}else{
for(int i = l; i <= ed[id[l]]; ++i){
arr[i] += x;
}
sum[id[l]] += (id[l]-l+1) * x;
for(int i = st[id[r]]; i <= r; ++i){
arr[i] += x;
}
sum[id[r]] += (r-id[r]+1) * x;
for(int i = id[l]+1; i < id[r]; ++i){
mark[i] += x;
}
}
}
3. 查询 \(O(\sqrt{N})\)
查询和修改的基本思路相同,注意一点,无论是操作零散点还是整个分块,都需要加上mark数组。
int query(int l, int r){
int ans = 0;
if(l == r){
for(int i = l; i <= r; ++i){
ans += arr[i];
}
ans += mark[id[i]] * (r-l+1);
}else{
for(int i = l; i <= ed[id[l]]; ++i){
ans += arr[i];
}
ans += mark[id[l]] * (ed[id[l]]-l+1);
for(int i = st[id[r]]; i <= r; ++i){
ans += arr[i];
}
ans += mark[id[r]] * (r-st[id[r]]+1);
for(int i = id[l]+1; i < id[r]; ++i){
ans += sum[i] + mark[i] * (ed[i]-st[i]+1);
}
}
return ans;
}