数组分块(经典根号算法之一)

本文介绍了分块算法的基本知识,包括原理、可实现的操作及分块方法。分块是一种将 O(n) 暴力过程变为 O(n1/2) 的优雅暴力算法,适用于区间问题。还给出了区间修改与查询的代码实现,并通过两个例题展示了分块模板的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.分块的基本知识

分块的原理

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值