分块基础

分块基础

分块思想

分块是一种思想,而不是一种数据结构。
分块的基本思想是,通过对原序列的划分,并在划分后的每一个块上预处理部分信息,从而较一般的暴力算法取得更优的时间复杂度。

——OI Wiki

区间修改,区间查询

预处理

  • 将原序列分成若干段,对于每一个「块」预处理它 的和,左右端点

  • 对于没一个元素预处理它所处的「块」的编号

  • 如果块的长度是\(len\),那显然块的数量\(num\)就是\(\lceil\frac{n}{len}\rceil\)

  • 对于第\(i\)个「块」它的

    \[L[i]=(i-1)*len+1 \]

    \[R[i]=i*len \]

需要注意的是最后一个块的右端点可能超过n要判断

修改和查询

修改

  • 增加区间\(l\)\(r\)的值可以发现它一定是一些完整的「块」和两个不完整「块」(可能没有)
  • 那么对于每一个不完整的「块」暴力修改区间内的 \(a[i]\) 值,再修改这个「块」的和
  • 对于完整的「块」无法暴力修改,可以打懒标记 \(lazy[i]\) 表示第i个分块集体加上了 \(lazy[i]\)

查询

  • 对于不完整的「块」和修改操作一样暴力累加,还有一个懒标记,需要加上懒标记\(*\)覆盖的区间长度
  • 对于完整的「块」加上这个「块」的值 和懒标记\(*\)区间长度

时间复杂度分析

对于每一次修改和查询复杂度是$$O(len+\frac{n}{len})$$
也就是不完整的「块」的长度加上中间块的个数
可以发现这是 \(len\)\(\sqrt{n}\) 时复杂度最优
所以每一次的复杂度约为

\[O(\sqrt{n}) \]

code

#include<bits/stdc++.h>
#define int long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
using namespace std;
const int N=1e5+10;
int L[N],R[N],pos[N],sum[N],a[N];
int lazy[N];
int n,T; 
void init(){
	int len=sqrt(n);
	int num=(n+len-1)/len;
	for(int i=1;i<=num;i++){
		L[i]=(i-1)*len+1;
		R[i]=i*len;
	}
	if(R[num]>n)R[num]=n;
	for(int i=1;i<=num;i++){
		for(int j=L[i];j<=R[i];j++){
			pos[j]=i;
			sum[i]+=a[j];
		}
	}
	return;
}
void update(int l,int r,int val){
	int p=pos[l],q=pos[r];
	if(p==q){
		for(int i=l;i<=r;i++){
			a[i]+=val;
		}
		sum[p]+=(r-l+1)*val;
		return;
	}
	for(int i=l;i<=R[p];i++)a[i]+=val;
	sum[p]+=(R[p]-l+1)*val;
	for(int i=L[q];i<=r;i++)a[i]+=val;
	sum[q]+=(r-L[q]+1)*val;
	for(int i=p+1;i<=q-1;i++)lazy[i]+=val;
	return;
}
int query(int l,int r,int c){
	int p=pos[l],q=pos[r];
	int res=0;
	if(p==q){
		for(int i=l;i<=r;i++)res=(res+a[i])%c;
		res+=lazy[p]*(r-l+1);
		res%=c;
	}
	else{
		for(int i=l;i<=R[p];i++)res=(res+a[i])%c;
		res+=(R[p]-l+1)*lazy[p];
		res%=c;
		for(int i=L[q];i<=r;i++)res=(res+a[i])%c;
		res+=(r-L[q]+1)*lazy[q];
		res%=c;
		for(int i=p+1;i<=q-1;i++){
			res+=sum[i]+lazy[i]*(R[i]-L[i]+1);
			res%=c;
		}
	}
	return res%c;
} 
signed main(){
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	T=n;
	init();
	while(T--){	
		int orr,l,r,c;
		cin>>orr>>l>>r>>c;
		if(orr==0)update(l,r,c);
		else cout<<query(l,r,c+1)<<'\n';
	}
	return 0;
}



区间修改,区间小值查询

思路

  • 可以考虑维护每一个「块」内的数,将它们从小到大排序,那么「块」内小于 \(k\) 的数的数量就是第一个大于等于这个数的数在「块」内排名
  • 每一次修改时对完整「块」记录懒标记,对不完整的「块」暴力修改每个数,然后重新将「块」内的数排名
  • 查询是对于不完整的「块」暴力记录大于 \(k\) 的数的个数,对于完整的「块」因为它们都加了 \(lazy[i]\) 所以实际上要找的是小于\(k-lazy[i]\)的数的个数,用lower_bound找到第一个大于等于这个数的数的排名,记录

code

#include<bits/stdc++.h>
#define int long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
using namespace std;
const int N=1e5+10;
int n,T;
int a[N],pos[N],L[N],R[N],lazy[N];
vector<int>G[N];
void init(){
	int len=sqrt(n);
	int num=(n+len-1)/len;
	for(int i=1;i<=num;i++){
		L[i]=(i-1)*len+1;
		R[i]=i*len;
	}
	if(R[num]>n)R[num]=n;
	for(int i=1;i<=num;i++){
		for(int j=L[i];j<=R[i];j++){
			pos[j]=i;
			G[i].push_back(a[j]);
		}
		sort(G[i].begin(),G[i].end());
	}
	return;
}
void EA(int l,int r,int k){
	int p=pos[l];
	for(int i=l;i<=r;i++)a[i]+=k;
	G[p].clear();
	for(int i=L[p];i<=R[p];i++)G[p].push_back(a[i]);
	sort(G[p].begin(),G[p].end());
	return;
}
void update(int l,int r,int k){
	int p=pos[l],q=pos[r];
	if(p==q){
		EA(l,r,k);
		return;
	}
	EA(l,R[p],k);
	EA(L[q],r,k);
	for(int i=p+1;i<=q-1;i++)lazy[i]+=k;
	return;
}
int query(int l,int r,int k){
	int cnt=0;
	for(int i=l;i<=r;i++){
		if(a[i]+lazy[pos[l]]<k)cnt++;
	}
	return cnt;
}
int ask(int l,int r,int k){
	int p=pos[l],q=pos[r];
	if(p==q)return query(l,r,k);
	int res=0;
	res+=query(l,R[p],k)+query(L[q],r,k);
	for(int i=p+1;i<=q-1;i++){
		res+=lower_bound(G[i].begin(),G[i].end(),k-lazy[i])-G[i].begin();
	}
	return res;
}
signed main(){
	ios::sync_with_stdio(false);
	cin>>n;
	T=n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	init();
	while(T--){
		int orr,l,r,c;
		cin>>orr>>l>>r>>c;
		if(orr==0)update(l,r,c);
		else cout<<ask(l,r,c*c)<<'\n';
	}
	return 0;
}



posted @ 2025-08-01 15:50  wmq2012  阅读(28)  评论(0)    收藏  举报