分块


分块

由于我在网上找不到定义,只好编一个。

分块 是一种将问题分解成若干个子问题,逐项解决子问题后得到原问题答案的思想。

考虑这样一个问题。

你有一个序列 \(a[1...N]\),你需要写一个数据结构维护它,支持以下操作:

  1. 修改 \(a[x]\) 的值;
  2. 查询 \(a[L...R]\) 的最大值、和、按位与和、或和、异或和、与非和、或非和、异或非和。

事实上,这道题可以加上很多很多个操作,这样 线段树 也无能为力了。


我们不妨想想如何优化朴素程序。
以求和为例,朴素程序写法如下:

	for(int i=L;i<=R;++i)
		ans+=a[i];	//flag

flag 行一共被执行了 \(R-L+1\) 次,这不是我们希望看到的。
如果是这样写呢?

	for(int i=L;i<=R;i+=2)
		ans+=a[i]+a[i+1];	//flag
	if((R-L+1)&1) ans-=a[R+1];

不难想到,flag 行只被执行了原来次数的一半。

设 flag 行将连续的 \(d\)\(a\) 累加给 \(ans\),则一共需要执行 flag 行 \(\frac{R-L+1}{d}\) 次。

如果我们用一个变量维护这 \(d\) 个数,那么我们查询的时间复杂度就是 \(O(\frac{R-L+1}{d})\),修改操作的时间复杂度是 \(O(d)\)

不妨令两操作次数相同,则程序有最低复杂度时,$$\frac{R-L+1}{d}=d$$即$$d=\sqrt{R-L+1}$$

换言之,当 \(d\) 的值等于操作区间(一般是 \([1,N]\))的 \(\frac12\) 次方时,该程序的时间复杂度最低,是 \(O(N^\frac12)\)(单次执行)。

我们不妨将被一并处理的 \(d\) 个数视作一块,那么原区间就被分成了 \(\sqrt N\) 块。

举个例子。我们现在要维护一个序列的和,分块后每个块的信息如下:
在这里插入图片描述
其中 \(a[i]\) 表示第 \(i\) 个块内每个数的和。



至此, 分块 已经介绍完毕了。下面我们看一道例题。

例题 您需要写一种数据结构,维护一个有序数列,支持以下操作:

  1. 查询 \(k\) 在区间内的排名;
  2. 查询区间内排名为 \(k\) 的值;
  3. 修改某一位值上的数值;
  4. 查询 \(k\) 在区间内的前驱(前驱定义为严格小于 \(x\),且最大的数);
  5. 查询 \(k\) 在区间内的后继(后继定义为严格大于 \(x\),且最小的数)。
参考代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>


using namespace std;

const int MAXN=50010;
const int MAXE=230;
const int MAXA=(int)1e8;

int n,m,sq;
int sx,sy,sd;

struct point{
	int x,y;
	friend bool operator<(const point a,const point b){
		return a.y<b.y;
	}
}a[MAXN];
struct split{
	int l,r;
	int mininum;
	point e[MAXE];
	void update(){
		sort(e+1,e+(r-l+1)+1);
		mininum=e[1].y;
	}
	void build(point p[MAXN],int L,int R){
		if(R>n) R=n;
		l=L;r=R;
		for(int i=l;i<=r;++i)
			e[i-l+1]=p[i];
		update();
	}
	int query(int x){
		int left=1,right=(r-l+1),mid,sum=0;
		while(left<=right){
			mid=(left+right)/2;
			if(e[mid].y<x){
				sum=mid;
				left=mid+1;
			}else
				right=mid-1;
		}
		return sum;
	}
	int que(int left,int right,int x){
		int cnt=0;
		for(int i=l;i<=r;++i)
			if(e[i-l+1].x>=max(l,left)&&e[i-l+1].x<=min(r,right)&&e[i-l+1].y<x) ++cnt;
		return cnt;
	}
	int check(int left,int right,int x){
		if(left<=l&&r<=right)
			return mininum==x;
		for(int i=max(l,left);i<=min(r,right);++i)
			if(e[i-l+1].y==x) return 1;
		return 0;
	}
	void change(int x,int d){
		for(int i=1;i<=r-l+1;++i)
			if(e[i].x==x){
				e[i].y=d;
				break;
			}
		update();
	}
}s[MAXE];
int len=0;

int getnum(int x){
	return (x-1)/sq+1;
}
int rank(int l,int r,int x){
	int L=getnum(l),R=getnum(r);
	int cnt=0;
	for(int i=L+1;i<=R-1;++i)
		cnt+=s[i].query(x);
	cnt+=s[L].que(l,r,x);
	if(L!=R) cnt+=s[R].que(l,r,x);
	if(!cnt){
		for(int i=L;i<=R;++i)
			if(s[i].check(l,r,x))
				return cnt+1;
		return 1;
	}
	return cnt+1;
}
int find(int l,int r,int x){
	int left=0,right=MAXA,mid,s=0;
	while(left<=right){
		mid=(left+right)/2;
		if(rank(l,r,mid)<=x){
			s=mid;
			left=mid+1;
		}else
			right=mid-1;
	}
	return s;
}
void change(int x,int d){
	s[getnum(x)].change(x,d);
}
int findupper(int l,int r,int x){
	int rk=rank(l,r,x);
	if(rk>r-l+1) return 2147483647;
	bool flag=(find(l,r,rk)==x);
	return find(l,r,rk+flag);
}
int findlower(int l,int r,int x){
	int rk=rank(l,r,x);
	if(rk<=1) return -2147483647;
	return find(l,r,rk-1);
}
inline int read(){
	int x=0; char c;
	do c=getchar(); while(c<'0'||c>'9');
	while(c>='0'&&c<='9')
		x=x*10+c-48,c=getchar();
	return x;
}
int main(){
	n=read();m=read();sq=sqrt(n);
	for(int i=1;i<=n;++i)
		a[i]=(point){i,read()};
	int cnt=0;
	while(cnt<n){
		s[++len].build(a,cnt+1,cnt+sq);
		cnt+=sq;
	}
	for(int i=1;i<=m;++i){
		sd=read();
		switch(sd){
			case 1:
				sx=read();sy=read();sd=read();
				int c;
				printf("%d\n",c=rank(sx,sy,sd));
				break;
			case 2:
				sx=read();sy=read();sd=read();
				printf("%d\n",find(sx,sy,sd));
				break;
			case 3:
				sx=read();sd=read();
				change(sx,sd);
				break;
			case 4:
				sx=read();sy=read();sd=read();
				printf("%d\n",findlower(sx,sy,sd));
				break;
			case 5:
				sx=read();sy=read();sd=read();
				printf("%d\n",findupper(sx,sy,sd));
				break;
			default:
				break;
		}
	}
}

下面列出了几道难度较低的练习题,有兴趣的读者可自行尝试。


练习 1 您需要设计一种数据结构,维护一个序列 \(a[1...N]\),支持以下操作:

  1. \(a[x]\) 的值加 \(d\)
  2. 查询 \(a[L...R]\) 中小于等于 \(d\) 的数的个数。

练习 2 您需要设计一种数据结构,维护一个序列 \(a[1...N]\),支持以下操作:

  1. \(a[L...R]\) 的值加 \(d\)
  2. 查询 \(a[L...R]\) 中小于 \(a[x]\) 的最大的数。

练习 3luogu P3372)您需要设计一种数据结构,维护一个序列 \(a[1...N]\),支持以下操作:

  1. \(a[L...R]\) 的值加 \(d\)
  2. 查询 \(a[L...R]\) 的数的和。

练习 4 您需要设计一种数据结构,维护一个序列 \(a[1...N]\),支持以下操作:

  1. \(a[L...R]\) 的值开方(向下取整);
  2. 查询 \(a[L...R]\) 的数的和。

练习 5luogu P3373)您需要设计一种数据结构,维护一个序列 \(a[1...N]\),支持以下操作:

  1. \(a[L...R]\) 的值加 \(d\)
  2. \(a[L...R]\) 的值乘 \(d\)
  3. 查询 \(a[L...R]\) 的数的和。

练习 6 您需要设计一种数据结构,维护一个序列 \(a[1...N]\),支持以下操作:

  1. 查询 \(a[L...R]\) 的数有多少个与 \(d\) 相等。将 \(a[L...R]\) 的每个数改成 \(d\)


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM