数列分块学习笔记(锣鼓梳理额粉筷入门模板)
分块概念:
我们称之为数列分块,对于块长为 \(\mathcal {\sqrt n}\) 的数列分块就可以称之为根号分治。
分块被称为 “优雅的暴力” , 真的很优雅,而且真的很暴力。
有著名的等式:
对于一些 \(\mathcal {O(n)}\) 不好处理的数列操作(后面还会有树操作,暂且不谈),而且操作相对来说比较复杂,像线段树这样比较呆傻的结构无法维护的问题,我们一般都使用分块来分治以AC题目。分块的优势就在于其有着非常强的扩展性,因为分块严格来说不是一个数据结构,而是一种分治的思想。你甚至可以在 bitset 传递闭包的时候看到分块。
现在要来学习的是根号分治!!
数列分块入门1
我们预处理出每个点所在的区块,预处理每个区块的左端点和右端点。
对于添加操作,我们先判断是否在同一区间,如果是的话就在区间里面暴力重构。
如果不是,那么就对于一整块要处理的区间的左边神域和右边神域进行暴力。
然后对于中间的整块部分我们直接 \(lazy\_tag\) 。
如果是查询的话,我们直接返回它的数列值加上它的 \(tag\) 值就好了。
整体的时间复杂度是 \(\mathcal O(n\sqrt n)\) .
下面的代码里:
a[] 是原数组;
len 是块长,我们这里根号分治;
pos[] 是下标对应的块的编号;
L[],R[] 分别存储某个块的左右端点下标;
tag[] 是整块的懒标记。
给出代码(进行了压行处理,需要的可以自己展开):
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=3e6+10;
int n,a[N],pos[N],L[N],R[N],len,tag[N];
void init(){
len=(int)sqrt(n);
for(int i=1;i<=n;i++)pos[i]=(i-1)/len+1;
for(int i=1;(i-1)*len+1<=n;i++)L[i]=(i-1)*len+1, R[i]=i*len;
}
void update (int l,int r,int c){
if(pos[l]==pos[r]){
for(int i=l;i<=r;i++)a[i]+=c;
return;
}
for(int i=l;i<=R[pos[l]];i++)a[i]+=c;
for(int i=L[pos[r]];i<=r;i++)a[i]+=c;
for(int i=pos[l]+1;i<pos[r];i++)tag[i]+=c;
}
int query(int x){return a[x]+tag[pos[x]];}
main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n;
init();
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
int l,r,opt,x;
cin>>opt>>l>>r>>x;
if(opt==0)update(l,r,x);
else cout<<query(r)<<endl;
}
}
数列分块入门2
我们要处理区间的查询,要分块思想的话我们就得对这个整块的查询加速,很傻逼的我们肯定选择对块内排序加上 \(lower\_bound\) 。
这里的时间复杂度是 \(\mathcal O(\sqrt n \log \sqrt n)\).
然后我们回来考虑如何对于块进行处理。预处理还是和先前的 “数列分块入门1” 是一样的。但是我们要新写一个对于块内 sort 的函数:
//此处x是块的编号
//L,R分别是区块左右端点
//a是原数组,b是sorted数组
//sort注意不要写错begin和end
void Sort(int x){
for(int i=L[x];i<=R[x];i++)b[i]=a[i];
sort(b+L[x],b+R[x]+1);
}
然后对于这里面的查询,我们还是遵循整块查询,散块暴力。
对于整块我们 res+=(lower_bound(b+L[i],b+1+R[i],x-tag[i])-b-L[i]); (自己意会)。
散块暴力查询 if(a[i]+tag[pos[i]]<x)res++; 解决。
警示后人:一开始输入完就要对所有的块 sort 一遍,而且在 update 的时候对于整块不用 sort ,对于散块要 sort ,保证 b[] 是有序的。
这里给出我们的代码(同样压行处理):
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N= 3e5+10;
int n,a[N],b[N],pos[N],L[N],R[N],tag[N],len;
void init(){
len=(int)sqrt(n);
for(int i=1;i<=n;i++)pos[i]=(i-1)/len+1;
for(int i=1;(i-1)*len+1<=n;i++)L[i]=(i-1)*len+1,R[i]=i*len;
}
void Sort(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 l,int r,int x){
if(pos[l]==pos[r]){
for(int i=l;i<=r;i++)a[i]+=x;
Sort(pos[l]);
return;
}
for(int i=l;i<=R[pos[l]];i++)a[i]+=x;
for(int i=L[pos[r]];i<=r;i++)a[i]+=x;
for(int i=pos[l]+1;i<pos[r];i++)tag[i]+=x;
Sort(pos[l]),Sort(pos[r]);
}
int query(int l,int r,int x){
int res=0;
if(pos[l]==pos[r]){
for(int i=l;i<=r;i++)if(a[i]+tag[pos[i]]<x)res++;
return res;
}
for(int i=l;i<=R[pos[l]];i++)if(a[i]+tag[pos[i]]<x)res++;
for(int i=L[pos[r]];i<=r;i++)if(a[i]+tag[pos[i]]<x)res++;
for(int i=pos[l]+1;i<pos[r];i++)res+=(lower_bound(b+L[i],b+1+R[i],x-tag[i])-b-L[i]);
return res;
}
main(){
cin>>n;
init();
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;(i-1)*len+1<=n;i++)Sort(i);
for(int i=1,opt,l,r,c;i<=n;i++){
cin>>opt>>l>>r>>c;
if(opt==0)update(l,r,c);
else cout<<query(l,r,c*c)<<endl;
}
}
数列分块入门3
直接可以在 “数列分块入门2” 的原代码的基础上进行改动。
我们在 query 中进行如下的写:
int query(int l,int r,int x){
int res=-1e18;
if(pos[l]==pos[r]){
for(int i=l;i<=r;i++)if(a[i]+tag[pos[i]]<x)res=max(res,a[i]+tag[pos[i]]);
return res==-1e18?-1:res;
}
for(int i=l;i<=R[pos[l]];i++)if(a[i]+tag[pos[i]]<x)res=max(res,a[i]+tag[pos[i]]);
for(int i=L[pos[r]];i<=r;i++)if(a[i]+tag[pos[i]]<x)res=max(res,a[i]+tag[pos[i]]);
for(int i=pos[l]+1;i<pos[r];i++){
int w=lower_bound(b+L[i],b+R[i]+1,x-tag[i])-b-1;
if(w>L[i]-1)res=max(res,b[w]+tag[i]);
}
return res==-1e18?-1:res;
}
其中整块部分的判断尤其重要,可以长考一下。
我们别的部分都和之前的代码一模一样,这里就不给出了。
数列分块入门4
单纯地进行一个整块查询散块暴力就好。
话虽如此,我们在块内要维护的是当前块的参差不齐的数值的总值,和 \(tag[]\) 数组。
在 \(query\) 的时候我们需要先把所有的都加起来然后再 \(\operatorname {mod}\) ,不然会被卡常卡掉。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 301010;
int n, a[N], L[N], R[N], pos[N], tag[N], sum[N];
int len, block_num;
void init() {
len = (int)sqrt(n);
block_num = (n + len - 1) / len;
for (int i = 1; i <= n; i++) pos[i] = (i - 1) / len + 1;
for (int i = 1; i <= block_num; i++) {
L[i] = (i - 1) * len + 1;
R[i] = min(i * len, n);
sum[i] = 0;
for (int j = L[i]; j <= R[i]; j++) sum[i] += a[j];
}
}
void update(int l, int r, int x) {
int p = pos[l], q = pos[r];
if (p == q) {
for (int i = l; i <= r; i++) a[i] += x;
sum[p] += (r - l + 1) * x;
return;
}
for (int i = l; i <= R[p]; i++) a[i] += x;
for (int i = L[q]; i <= r; i++) a[i] += x;
sum[p] += (R[p] - l + 1) * x;
sum[q] += (r - L[q] + 1) * x;
for (int i = p + 1; i < q; i++) tag[i] += x;
}
int query(int l, int r, int c) {
int mod = c + 1, res = 0, p = pos[l], q = pos[r];
if (p == q) {
for (int i = l; i <= r; i++) res += a[i];
res += (r - l + 1) * tag[p];
return (res % mod + mod) % mod;
}
for (int i = l; i <= R[p]; i++) res += a[i];
for (int i = L[q]; i <= r; i++) res += a[i];
res += (R[p] - l + 1) * tag[p];
res += (r - L[q] + 1) * tag[q];
for (int i = p + 1; i < q; i++) res += sum[i] + (R[i] - L[i] + 1) * tag[i];
return (res % mod + mod) % mod;
}
signed main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; cin >> a[i], i++) ;
init();
for (int i = 1, opt, l, r, c; i <= n; i++) {
cin >> opt >> l >> r >> c;
if (opt == 0) update(l, r, c);
else cout << query(l, r, c) << endl;}
return 0;
}

浙公网安备 33010602011771号