数列分块学习笔记(锣鼓梳理额粉筷入门模板)

分块概念:

我们称之为数列分块,对于块长为 \(\mathcal {\sqrt n}\) 的数列分块就可以称之为根号分治。
分块被称为 “优雅的暴力” , 真的很优雅,而且真的很暴力。
有著名的等式:

\[\LARGE {暴力+暴力=分块} \]

对于一些 \(\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;
}
posted @ 2025-10-31 18:18  Noivelist  阅读(18)  评论(0)    收藏  举报