猫史档案馆


复杂离线分治算法:值域整体分治

用户:爵士OIer爵士OIer查看:308 回复:28 评论:36 创建时间:2021-08-08T11:41:28


离线维护的整体分治算法是一类复杂分治,能够处理二维数据结构和可持久化线段树维护的一些信息,并且代码量相对而言较小,易于理解,是一个好的算法。

 

 

 

什么是离线算法

 

 

很多需要处理的信息可以抽象为两类操作。第一类操作是修改,第二类操作则是查询。

 

如果一些需要处理的信息中,所有的修改操作都在查询操作之后,或者只需要查询操作,则称这类问题为静态的。其余问题被称作动态的。

离线算法是指,将所有的操作统一写入之后,经过一系列计算,一次性回答所有的询问操作。

 

基于值域的整体分治算法就是一个能够动态维护信息的离线算法。

 

 

 

前置数据结构:树状数组

 

 

这里先略微介绍一下树状数组。

 

树状数组用于维护序列的前缀和,其基本思想为对区间结尾进行二进制分解,将区间划分为2次幂的长度的小区间。

分成的区间是 O (log x) 级别的,能够保证其时间复杂度。

center_image

我们可以使用 Lowbit(x) 运算进行快速划分。

 

树状数组查询前缀和

//树状数组查询前缀和
int ask(int x){
	int ans=0;
	for(;x;x-=x&-x)ans+=c[x];
	return ans;
}

 

树状数组单点修改

// 树状数组单点修改
void add(int x,int y){
	for (;x<=N;x+=x&-x)c[x]+=y;
}

 

 

 

基于值域的整体分治算法

 

 

这类离线分治算法对整个操作序列进行分治,使操作序列在保持时间顺序的基础上和值域一起被划分为独立的子问题,从而维护信息。

故称之为基于值域的整体分治算法,或曰整体分治。

 

 

K-th问题

 

动态区间Kth是一个经典问题。我们将其抽象为两类操作。

2 x y k 表示查询区间 [x,y] 内的第 k 小数。

1 x y 表示将位置为 x 的数修改为 y。

我们要求时间复杂度应在 O(m log n) 内解决。

 

 

基本思想

 

对于一操作 1,选取一个值 mid。我们如果能够快速求出 [x,y] 中 <=mid 的数的个数,记为 cnt,则我们就能够知道 Kth 在哪一个值域的范围内了。具体而言:

1. 若 cnt>=k,说明 <= mid 的数大于 k 个,因此 Kth 的值 <=mid;

2. 若 cnt<k,说明 <=mid 的数大于 k 个,因此 Kth 的值 >mid。

 

因此我们二分这个值域。center_image

因此我们将操作序列按其操作的数的值,在二分的过程中分别放到两个子操作序列中,和值域一起二分。

 

 

算法流程

 

我们得到如下算法:

1. 对于一个修改操作,将其与值域的 mid 比较,如果其值 <=mid 则在原序列的位置上 +1,并放到子操作序列 lq 中。否则直接放到 rq 中。

2. 对于一个询问操作,我们获得在 [x,y] 中 <=mid 的个数 cnt,按照前述方法操作。

3. 对操作序列划分完成之后,用两个子序列去更新原操作序列,并还原树状数组。

4. 分治求解两个值域。

5. 分治的边界为 lval=rval,此时我们就得到了 [st,ed] 这一段操作的询问的结果。

 

其中我们需要支持单点修改、区间查值,这用前面提到的树状数组维护即可。

 

 

操作处理

 

我们将输入序列视作一次插入操作。

修改单点视作两次修改操作,一次为删除,一次为插入。

 

上述算法就是值域上整体分治的基本思想和一类解决流程。

 

struct rec{int op,x,y,z;}q[N],lq[N],rq[N];
int T,n,m,t,p,a[N],c[N],ans[N];
int ask(int x){
	int y=0;
	for(;x;x-=x&-x)y+=c[x];
	return y;
}
void change(int x,int y){
	for(;x<=n;x+=x&-x)c[x]+=y;
}
void solve(int lval,int rval,int st,int ed){
	if(st>ed)return;
	if(lval==rval){
		for(int i=st;i<=ed;i++)
			if(q[i].op>0)ans[q[i].op]=lval;
		return;
	}
	int mid=lval+rval>>1,lt=0,rt=0;
	for(int i=st;i<=ed;i++){
		if(q[i].op<=0){
			if(q[i].y<=mid)
				change(q[i].x,q[i].z),lq[++lt]=q[i];
			else rq[++rt]=q[i];
		}
		else{
			int cnt=ask(q[i].y)-ask(q[i].x-1);
			if(cnt>=q[i].z)lq[++lt]=q[i];
			else q[i].z-=cnt,rq[++rt]=q[i];
		}
	}
	for(int i=ed;i>=st;i--)
		if(q[i].op<=0&&q[i].y<=mid)
			change(q[i].x,-q[i].z);
	for(int i=1;i<=lt;i++)q[st+i-1]=lq[i];
	for(int i=1;i<=rt;i++)q[st+lt+i-1]=rq[i];
	solve(lval,mid,st,st+lt-1);
	solve(mid+1,rval,st+lt,ed);
}

//主函数中
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		int val=read();
		q[++t].op=0,q[t].x=i,q[t].y=val,
		q[i].z=1,a[i]=val;
	}
	for(int i=1;i<=m;i++){
		char op[5];scanf("%s",op);
		if(op[0]=='Q'){
			int l=read(),r=read(),k=read();
			q[++t].op=++p,q[t].x=l,q[t].y=r,q[t].z=k;
		}
		else{
			int x=read(),y=read();
			q[++t].op=-1,q[t].x=x,q[t].y=a[x],q[t].z=-1,
			q[++t].op=0,q[t].x=x,q[t].y=y,q[t].z=1,a[x]=y;
		}
	}
	solve(0,INF,1,t);


回复

上一页第 1 页 / 共 1 页下一页
硅_约瑟夫硅_约瑟夫

好贴,沙发

点赞0


评论

az


伴雪纷飞伴雪纷飞

占位

点赞0


评论


小鱼yuzifu小鱼yuzifu

nb,顶顶

点赞0


评论


爵士OIer爵士OIer

ddd

点赞0


评论


阳光的流熔怪Uyt5阳光的流熔怪Uyt5

我想活着QaQ

树状数组可不可以理解为树形的前缀和,一次减去一半就只有log n的复杂度?

点赞0


评论

az

树状数组其实名字是“树状”,但是核心是位运算的Lowbit

lowbit(x)=x&-x

数组c[x]保存区间[x-lowbit(x)+1,x]的和

是这么划分的

lowbit(x)表示x在二进制下最低位1的值对吧,但为什么要这样分?

我真是个山本诶,这喵就是二分

假设区间长度是x,那么x的二进制拆分的每一段(假设结尾是y)的长度都是Lowbit(y)

我觉得更是倍增(

只是形式上,因为树状数组也不是倍增= =

(凎,我在学信竞的时候摸鱼来学信竞)


Mellin_AmpMellin_Amp

千人喵下期讲树状数组(1/1000)

点赞3


评论


爵士OIer爵士OIer

ddd

点赞0


评论


AlcalaAlcala

ddd

点赞0


评论

我才学到二叉查找树,%%%

是用来查询排名、插入删除、求前驱后继的二叉搜索树吗?

对,大佬能帮我调一下这道题吗喵record/54919257()

www.luogu.com.cn/record/54919257

要代码吗?)

我无权查看记录(

代码发了)

是哪题啊

P1801)

这个用整体分治就能做()

动态整体Kth你用平衡树、权值线段树、整体分治都能够做

还有不建议用二叉查找树写,因为二叉查找树不一定平衡,时间复杂度可能会退化

那些我都不会)

这个可以优化吗?)

优化之后就是平衡树了

整体分治难道不就是这篇教程吗()

az)

刚刚那题我用整体分治AC了

Orz


Asheep233Asheep233

ddd

点赞0


评论


Albert钟Albert钟

ddd

点赞0


评论


爵士OIer爵士OIer

ddd

点赞0


评论


AlcalaAlcala

#include<bits/stdc++.h>
#define endl ('\n')
using namespace std;
const int maxn = 2 * 1e5 + 1e2;
int iiiii,root,n,m,a[maxn],u[maxn],size,t[maxn][2],key[maxn],siz[maxn];
void insert(int &u,int x){
	if(u == 0){
		u = ++size,key[u] = x,siz[u] = 1;
		return;
	}
	int d = (x > key[u]);
	insert(t[u][d],x);
	siz[u]++;
}
int find(int u,int k){
	int s = siz[t[u][0]];
	if(k < s) return find(t[u][0],k);
	if(k == s) return key[u];
	return find(t[u][1],k - s - 1);
}
int order(int u,int x){
	if(u == 0) return 0;
	if(x <= key[u]) return order(t[u][0],x);
	return siz[t[u][0]] + 1 + order(t[u][1],x);
}
int qq(int root,int x){
	return find(root,order(root,x) - 1);
}
int hj(int root,int x){
	return find(root,order(root,x + 1));
}
int main(){
	ios::sync_with_stdio(false);
    cin.tie(0);
	cout.tie(0);
	cin>>m>>n;
	int now = 1;
	for(int i = 1;i <= m;i++){
		cin>>a[i];
	}
	for(int i = 1;i <= n;i++){
		cin>>u[i];
	}
	for(int i = 1;i <= m;i++){
		insert(root,a[i]);
		while(u[now] == i){
			now++;
			cout<<find(root,iiiii++)<<endl;
		}
	}
	return 0;
}

点赞0


评论


阳光的流熔怪Uyt5阳光的流熔怪Uyt5

ddd

点赞0


评论


爵士OIer爵士OIer

ddd

点赞0


评论


爵士OIer爵士OIer

ddd

点赞0


评论


AlcalaAlcala

下次讲平衡树?)

点赞0


评论

这可以

好耶)


33aaron33aaron

ddd

点赞0


评论


气人训练师扣子气人训练师扣子

《易于理解》

点赞1


评论

az


爵士OIer爵士OIer

ddd

点赞0


评论


珂朵莉Chtholly珂朵莉Chtholly

ddd

点赞0


评论


爵士OIer爵士OIer

ddd 大家都来看看

其实仔细想想还是挺好理解的

点赞0


评论


一只萌新小猫一只萌新小猫

编程猫有关算法的帖子寥寥无几........

点赞1


评论

az


爵士OIer爵士OIer

ddd

点赞0


评论


爵士OIer爵士OIer

ddd

点赞0


评论


一只萌新小猫一只萌新小猫

《易于理解》

点赞0


评论


一只萌新小猫一只萌新小猫

《代码量相对而言较小》

点赞0


评论

确实挺小的吧。。


爵士OIer爵士OIer

喵d

点赞0


评论


爵士OIer爵士OIer

ddd

点赞0


评论