#edu3001. 分块-byx

分块-byx

Update:2025.5.25


树状数组是基于二进制划分与倍增的思想,线段树基于分治的思想。之所以能够高效修改和查询,就是把序列分成了大大小小的“段”,花费额外(增加空间,空间换时间)的代价对这些“段”管理的信息进行记录和更新,查询一个区间的信息,可以将这个区间分成几个已有的“段”结合,从而提高效率。

树状数组和线段树也有缺点,当需要维护一些不满足区间可加性、可减性信息时显得吃力,代码也不直观,需要深入理解并注意很多细节。

当维护的信息不满足区间合并性,例如区间众数,要提高效率,就需要对序列进行分块,分块的基本思想就是“大段维护,局部暴力”,预处理一些信息并保存起来,用空间换时间,分块更加通用、容易实现

经典例题:

例1: 数列分块入门 1

题意:给出一个长为 n(1n50000)n(1 \le n\le 50000) 的数列,以及 nn 个操作,操作涉及区间加法,单点查值。

分析:可以利用树状数组和线段树在 O(NlogN)O(N\log{N}) 的时间解决,现在我们使用分块解决本问题。

首先我们设块长为 LL,那么块的个数就是 t=n/Ln/Lt=\left \lceil n/L \right \rceil \approx n/L 。对于任意一个区间 (l,r)(l,r),区间内整段只需要对其 lazylazy 标记数组修改,对于开头、结尾不足整段的部分进行朴素修改,单次区间修改复杂度可以表示为 O(L+t)O(L+t),那么根据不等式的性质:

L+t=L+n/L2×L(n/L)=2nL+t=L+n/L \ge 2 \times \sqrt{L*(n/L)}=2\sqrt{n}

L=n/LL=n/L 时,不等式等号成立,即 L=nL=\sqrt{n}

nn 次修改复杂度为 O(nn)O(n\sqrt{n}), 单点询问复杂度 O(1)O(1).

参考代码:点击

例2: 数列分块入门 2

题意:给出一个长为 n(1n50000)n(1 \le n\le 50000) 的数列,以及 nn 个操作,操作涉及区间加法,询问区间内小于某个值 xx 的元素个数。

分析:询问区间内小于 xx 的个数,如果不带修改,可以利用主席树解决,带上修改后,主席树维护起来也很困难,考虑使用分块。

分块后,可以将块内元素排序(不破坏原序列,使用辅助数组 或 vector),当询问时,整块内可以二分查找快速解决,开头和结尾部分朴素查询,那么单次询问复杂度就是 O(L+tlogL)O(L+t*\log{L}) .

区间修改,对于整块,直接修改 lazylazy 标记,开头和结尾部分朴素修改,然后重构辅助数组。

参考代码:点击

例3:P4168蒲公英

题意:给定一个长度为 nn 的序列, mm 次询问区间众数。(1n40000,1m50000,1S,512M)(1 \leq n \leq 40000,1 \leq m \leq 50000,1S,512M)

本题是经典的在线区间众数的问题(不修改,只询问,在线)。区间众数不具有“区间可加性”,用树状数组、线段树维护就很困难。可以用分块加上计数桶数组的思想解决。

定义 s[i][j]s[i][j]iijj 出现的次数,f[i][j]f[i][j] 第i块到第j块的众数,提前预处理计算出这两个数组,时间复杂度$O(\sqrt{N}|a_i|+\sqrt{N}\times \sqrt{N} \times \sqrt{N})$,ai|a_i| 表示 aia_i 的值域,离散化后就是 NN

需要使用一个临时计数数组 t[j]t[j] 记录 jj 出现的次数。为了清零,不要每次使用的时候都memset,使用完后,修改了那一段整队那一段清零。

询问[l,r][l,r]区间众数,如果 p=l/len,q=r/lenp=l/len,q=r/len( pp,qq 表示所处的块编号),第 p+1p+1q1q-1 块是整段包含的,众数就是 f[i][j]f[i][j], 局部[led[p])[l,ed[p])[st[q],r][st[q],r]扫描每一个数,计算其出现的次数,保留出现次数最多的数就是答案,查询时间复杂度就是 O(m×(N+N))O(m \times ( \sqrt{N}+ \sqrt{N}))

参考代码:点击


其他例题:

P3372 【模板】线段树 1

//分块实现区间修改、区间查询
//线段树模板-1
 
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const int T=1000;
int n,m,a[N],belong[N];
int t,len; //t块 =ceil(sqrt(t))
long long  sum[T],add[T],st[T],ed[T];
//belong[i] 表示a[i] 处于第几块
//st[t]、ed[t] 表示第t快左端点、右端点 对应原数组下标 
void init()
{
	len=sqrt(n);
	t=len;
	for(int i=1;i<=t;i++)
	{
		st[i]=(i-1)*len+1;
		ed[i]=i*len;
	}
	if(ed[t]<n)
	{
		t++;
		st[t]=ed[t-1]+1;
		ed[t]=n;
	}
	for(int i=1;i<=t;i++)
		for(int j=st[i];j<=ed[i];j++)
		{
			belong[j]=i;
			sum[i]+=a[j];
		}	
}
void modify(int l,int r,int d)
{
	int p=belong[l],q=belong[r];
	if(p==q)
	{
		for(int i=l;i<=r;i++)a[i]+=d;
		sum[p]+=(r-l+1)*d;
	}
	else 
	{
		for(int i=p+1;i<=q-1;i++)add[i]+=d;
		
		for(int i=l;i<=ed[p];i++)a[i]+=d;
		sum[p]+=(ed[p]-l+1)*d;
        
		for(int i=st[q];i<=r;i++)a[i]+=d;
		sum[q]+=(r-st[q]+1)*d;
	}
}
long long query(int l,int r)
{
	int p=belong[l],q=belong[r];
	long long ret=0;
	if(p==q)
	{
		for(int i=l;i<=r;i++)ret+=a[i];
		ret+=(r-l+1)*add[p];
	}
	else
	{
		for(int i=p+1;i<=q-1;i++)ret+=sum[i]+add[i]*(ed[i]-st[i]+1);
		for(int i=l;i<=ed[p];i++)ret+=a[i];
		ret+=add[p]*(ed[p]-l+1);
		for(int i=st[q];i<=r;i++)ret+=a[i];
		ret+=add[q]*(r-st[q]+1);
	}
	return ret;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",a+i);
	
	init();
	
	while(m--)
	{
		int op,x,y,k;
		scanf("%d",&op);
		if(op==1)
		{
			scanf("%d%d%d",&x,&y,&k);
			modify(x,y,k);
		}
		else 
		{
			scanf("%d%d",&x,&y);
			printf("%lld\n",query(x,y));
		}		
	} 
	return 0;
}

P2801教主的魔法

题意:两种操作:

1.给区间[l,r][l,r]加一个数cc;

2.查询区间[l,r][l,r]大于等于cc的个数

分析:可以使用分块,每一块内排序,块内是有序的,可以利用二分查找,找的大于等于cc的个数,但是又不能在原始序列上排序,否则修改的到块的内部,就没办法修改了,可以利用增加一个辅助数组,长度与原始数组相等,在辅助数组内进行排序。 假如分成numnum块,则块长度为n/numn/num,块内直接暴力修改、查询,块整体修改,只需要在“增量数组”上修改就可以了,查询的时候在辅助有序数组上二分查找。q次修改、查询,时间复杂度就为:O(qnlogn)O(q\sqrt{n} \log_{}{n})

参考代码: 点击

本质就是区间第 kk 大问题,对于静态区间第 kk 大,使用可持久化线段树效率更高,具体可以参考 线段树2。

P4135作诗

题意:给定长度为 nn 的整数序列,mm 次询问区间 [l,r][l,r] 中出现正偶数次的数的个数。(1n,m105,512M1 \leq n,m \leq 10^5,512M)

预处理出:s[i][j]s[i][j] 为前 ii 块中 jj 出现的次数,f[i][j]f[i][j] 为第 ii 块到第 jj 块中出现偶数次的数的个数。

P5048 Ynoi2019 模拟赛 Yuno loves sqrt technology III

题意:给你一个长为 nn 的序列 aamm 次询问,每次查询一个区间的众数的出现次数,强制在线。(1n,m105,2S,62M1 \leq n,m \leq 10^5,2S,62M)

本题可以用蒲公英相同的思路解决。

其他分块练习题:

1.P5046 [Ynoi2019 模拟赛] Yuno loves sqrt technology I

题意:给你一个长为 nn 的排列,mm 次询问,每次查询一个区间的逆序对数,强制在线。

2.P5047 [Ynoi2019 模拟赛] Yuno loves sqrt technology II

题意:给你一个长为 nn 的序列 aamm 次询问,每次查询一个区间的逆序对数。

P6774 [NOI2020] 时代的眼泪

区间正序对加强版,分块+容斥

[P1494 国家集训队]小Z的袜子](https://www.luogu.com.cn/problem/P1494)

题意:给定一个长度为 NN 的序列,多次询问 [l,r][l,r] 任意抽中两个相同的数概率。

分析:直接利用分块,会T掉,可以利用莫队。

单点移动需要计算,区间 [l,r][l,r] 总共的方法数为 Crl+12C_{r-l+1}^2

抽中相同的方法数为 sumsumcol[x]col[x] 表示 xx 的个数。

当新增 xx ,抽中相同的方法数 sum+=col[x]sum+=col[x],然后 col[x]++col[x]++.

当删除 xx ,先col[x]++col[x]++, 相同的方法数 sum=col[x]sum-=col[x].

参考代码 :点击