平衡树三连击

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hwzzyr/article/details/78566132
普通、文艺以及二逼平衡树
       平衡树的题。
       这种数据结构的裸题,就是拿来虐我这种巨菜的(尤其是代码能力不强的同学)。
       首先说明,这三道题我都是用Splay写的,想要看Treap的同学可以出门右转了。
       那么先说第一道,普通平衡树。

普通平衡树

Description

  您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
  1. 插入x数
  2. 删除x数(若有多个相同的数,因只删除一个)
  3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
  4. 查询排名为x的数
  5. 求x的前驱(前驱定义为小于x,且最大的数)
  6. 求x的后继(后继定义为大于x,且最小的数)

Input

  第一行为n,表示操作的个数
  下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6)

Output

  对于操作3,4,5,6每行输出一个数,表示对应答案                                                                                                                       

Sample Input

10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598                                                                                                                        

Sample Output

106465
84185
492737                                                                                                                        

Hint

【数据规模与约定】1≤n≤10^​5​​ ,−10^​7​​ ≤x≤10^​7                                                                                                                        









































     看完题目我先是觉得没什么问题,当时认为即使有重复的元素,也可以用正常的单节点Splay来写。。。。。。
可是我很快意识到这似乎不行。。。(交了WA当然不行)就算是非要去写查找的时候一直向左或者是向右,但是代码很难写,而且时间复杂度似乎不行(也没写,感觉可能不只是常数的问题,复杂度可能玄学了)。后来就改写(重写)了一遍带相同节点标记的平衡树。说来也怪,写完交了就A过去了。。。
      
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
inline int read(){
	char c;int rec=0,f=1;
	while((c=getchar())<'0'||c>'9')if(c=='-')f=-1;
	while(c>='0'&&c<='9')rec=rec*10+c-'0',c=getchar();
	return rec*f;
}
int n,root,cnt;
struct Splay_Tree{
	int F,s[2],val;
	int d,size;
	inline void NewNode(int fa,int x){F=fa;val=x;d=1;size=1;return ;}
}tree[100005];
inline void Pushup(int v){
	tree[v].size=tree[tree[v].s[0]].size+tree[v].d+tree[tree[v].s[1]].size;return ;
}
inline void Rotate(int v){
	int p=tree[v].F,g=tree[p].F;
	int t1=(v==tree[p].s[1]),t2=(p==tree[g].s[1]),S=tree[v].s[!t1];
	tree[v].F=g;tree[g].s[t2]=v;
	tree[S].F=p;tree[p].s[t1]=S;
	tree[p].F=v;tree[v].s[!t1]=p;
	Pushup(p);return ;
}
inline void Splay(int v){
	while(tree[v].F){
		int p=tree[v].F,g=tree[p].F;
		if(g)(v==tree[p].s[1])^(p==tree[g].s[1])?Rotate(v):Rotate(p);
		Rotate(v);
	}Pushup(v);root=v;return ;
}
inline void Insert(int x){
	if(!root){tree[++cnt].NewNode(0,x);root=cnt;return ;}
	int v=root,f;
	while(v){
		f=v;
		if(x==tree[v].val){tree[v].d++;Splay(v);return ;}//相同的值累计点数就可以了
		v=tree[v].s[x>tree[v].val];
	}tree[++cnt].NewNode(f,x);tree[f].s[x>tree[f].val]=cnt;
	Splay(cnt);return ;
}
inline int Extreme(int v,int f){while(tree[v].s[f])v=tree[v].s[f];return v;}
inline int Pre(int v){v=tree[v].s[0];return Extreme(v,1);}
inline int Next(int v){v=tree[v].s[1];return Extreme(v,0);}
inline void Delete(int v){
	Splay(v);
	if(tree[v].d>1){tree[v].d--;tree[v].size--;return ;}
	int L=tree[v].s[0],R=tree[v].s[1];
	if(L==0&&R==0){root=0;return ;}
	if(L==0){root=R;tree[R].F=0;return ;}
	if(R==0){root=L;tree[L].F=0;return ;}
	int p=L;tree[L].F=0;p=Extreme(p,1);
	Splay(p);tree[p].s[1]=R;tree[R].F=p;
	Pushup(p);return ;
}
inline int Kth(int k){
	int v=root;
	while(tree[tree[v].s[0]].size+1>k||tree[tree[v].s[0]].size+tree[v].d<k){//因为有了点数,所以说这里的代码也就从两个值的判等变成了区间包含
		if(tree[tree[v].s[0]].size+1>k)v=tree[v].s[0];
		else {k-=tree[tree[v].s[0]].size+tree[v].d;v=tree[v].s[1];}
	}return v;
}
inline int Find(int x){
	int v=root,rank=0;
	while(tree[v].val!=x){
		if(tree[v].val>x)v=tree[v].s[0];
		else rank+=tree[tree[v].s[0]].size+tree[v].d,v=tree[v].s[1];
	}return rank+tree[tree[v].s[0]].size+1;
}
inline int Find_P(int x){
	int v=root;
	while(tree[v].val!=x)v=tree[v].s[x>tree[v].val];
	return v;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		int f=read(),x=read();
		if(f==1)Insert(x);
		if(f==2)Delete(Find_P(x));
		if(f==3)cout<<Find(x)<<'\n';
		if(f==4)cout<<tree[Kth(x)].val<<'\n';
		if(f==5){
			Insert(x);
			cout<<tree[Pre(root)].val<<'\n';
			Delete(root);
		}
		if(f==6){
			Insert(x);
			cout<<tree[Next(root)].val<<'\n';
			Delete(root);
		}
	}
	return 0;
}
            代码略长,感觉缩进风格还有点丑(总有人说我是压行选手。。。。)。
       查找一个数的前驱或者是后继的时候,如果不能确定有相应的节点已经在树里面了,那么就最好临时增加一个节点,直接求该节点的前驱或者是后继,然后再将其删除。这样的方法看上去常数大了些(看上去是3倍啊。。。),但是提高了正确性,思考和代码难度也相应的降低了。 (Delete似乎写错了???但是没挂,是不是有些什么玄学的地方没发现)

文艺平衡树

Description

  您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1

Input

  第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2……n-1,n) m表示翻转操作次数
  接下来m行每行两个数[L,R] 数据保证 1<=L<=R<=n

Output

  输出一行n个数字,表示原始序列经过m次变换后的结果                                                                                                                        

Sample Input

5 3
1 3
1 3
1 4                                                                                                                        

Sample Output

4 3 2 1 5                                                                                                                        

Hint

1≤n,m≤100000                                                                                                                        




























       来到了第二棵树,文艺平衡树。
       确实很文艺,整个题目只有一个操作(忽然想起某维护数列)
       只有一个打上Rev标记的操作。注意先打标记,并将当前节点一起翻转,当再次访问到这个节点的时候就把它的标记影响下传。注意的的区间类Splay需要加上的两个哨兵节点,这就导致需要在操作的时候给节点的编号加上1。我看似也想过把哨兵节点的size赋值为0,但是这样会导致一些奇怪的特判。。。最好还是放弃,毕竟读入的时候+1还是很简单的。
       我是手动将两个哨兵节点建好,所以最开始有好几句话写漏了(或者说写错了更好?),最后把它从主函数中剔出来,单独写了一个函数解决。
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
inline int read(){
	char c;int rec=0;
	while((c=getchar())<'0'||c>'9');
	while(c>='0'&&c<='9')rec=rec*10+c-'0',c=getchar();
	return rec;
}
int n,m;
int cnt,root;
struct Splay_Tree{
	int F,s[2],val,size;
	int Rev;
	inline void NewNode(int fa,int x){F=fa;val=x;size=1;return ;}
}tree[100005];
inline void Pushup(int v){
	tree[v].size=tree[tree[v].s[0]].size+1+tree[tree[v].s[1]].size;return ;
}
inline void Rev(int v){tree[v].Rev^=1;swap(tree[v].s[0],tree[v].s[1]);return ;}
inline void Pushdown(int v){
	if(tree[v].Rev){Rev(tree[v].s[0]);Rev(tree[v].s[1]);tree[v].Rev=0;}return ;
}
inline void Lazy(int v){if(tree[v].F)Lazy(tree[v].F);Pushdown(v);return ;}//一次性将标记下传完毕,不影响之后的Rotate 
inline void Rotate(int v){
	int p=tree[v].F,g=tree[p].F;
	int t1=(v==tree[p].s[1]),t2=(p==tree[g].s[1]),S=tree[v].s[!t1];
	if(g)tree[g].s[t2]=v;tree[v].F=g;
	tree[p].s[t1]=S;tree[S].F=p;
	tree[v].s[!t1]=p;tree[p].F=v;
	Pushup(p);return ;
}
inline void Splay(int v,int goal){
	Lazy(v);
	while(tree[v].F!=goal){
		int p=tree[v].F,g=tree[p].F;
		if(g!=goal)(v==tree[p].s[1])^(p==tree[g].s[1])?Rotate(v):Rotate(p);
		Rotate(v);
	}Pushup(v);if(!goal)root=v;return ;
}
inline int Kth(int k){
	int v=root;
	while(tree[tree[v].s[0]].size+1!=k){
		Pushdown(v);
		if (tree[tree[v].s[0]].size+1>k)v=tree[v].s[0];
		else {k-=tree[tree[v].s[0]].size+1;v=tree[v].s[1];}
	}return v;
}
inline int Prepare(int x,int y){
	int v=Kth(x);Splay(v,0);
	v=Kth(y);Splay(v,root);
	return v;
}//把相应的区间取出
inline void Reverse(int L,int R){int v=Prepare(L-1,R+1);Rev(tree[v].s[0]);return ;}
inline int Build(int pre,int L,int R){
	if(L>R)return 0;
	int mid=(L+R)>>1;
	tree[++cnt].NewNode(pre,mid);
	int p=cnt;
	tree[p].s[0]=Build(p,L,mid-1);
	tree[p].s[1]=Build(p,mid+1,R);
	Pushup(p);return p;
}
inline void Instant(){
	tree[++cnt].NewNode(0,0);
	tree[++cnt].NewNode(1,0);
	root=1;tree[1].s[1]=2;
	tree[2].s[0]=Build(2,1,n);
	Pushup(2);Pushup(1);return ;
}//一些细节
inline void Print(int v){
	if(v==0)return ;
	Pushdown(v);
	Print(tree[v].s[0]);
	if(tree[v].val)cout<<tree[v].val<<" ";
	Print(tree[v].s[1]);
	return ;
}
int main(){
	n=read();m=read();Instant();
	for(int i=1;i<=m;i++){
		int x=read(),y=read();
		Reverse(x+1,y+1);
	}
	Print(root);
	return 0;
}

二逼平衡树

Description

  您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:
  1.查询 x在区间内的排名;
  2.查询区间内排名为 k 的值;
  3.修改某一位置上的数值;
  4.查询 x 在区间内的前趋(前趋定义为小于 x,且最大的数);
  5.查询 x 在区间内的后继(后继定义为大于 x,且最小的数)。

Input

  第一行两个数 n,m,表示长度为 n 的有序序列和 m 个操作。
  第二行有 n个数,表示有序序列。
  下面有 m 行,每行第一个数表示操作类型:
    1.之后有三个数 l,r,x表示查询 x在区间 [l,r] 的排名;
    2.之后有三个数 l,r,k表示查询区间 [l,r]内排名为 k的数;
    3.之后有两个数 pos,x表示将 pos位置的数修改为 x;
    4.之后有三个数 l,r,x表示查询区间 [l,r]内 x 的前趋;
    5.之后有三个数 l,r,x表示查询区间 [l,r]内 x 的后继。

Output

  对于操作 1,2,4,5各输出一行,表示查询结果。                                                                                                                        

Sample Input

9 6
4 2 2 1 9 4 0 1 1
2 1 4 3
3 4 10
2 1 4 3
1 2 5 9
4 3 9 5
5 2 8 5                                                                                                                        

Sample Output

2
4
3
4
9                                                                                                                        

Hint

  1≤n,m≤5×10​^4​​ ,−10​^8​​ ≤k,x≤10^​8                                                                                                                        

 











































       事实上这道题并不是,或者说不完全是平衡树,可以有分块的做法,也可以写树套树来解决这个问题。
       有了第一题的经验,我们将第一题写好的普通平衡树封装好了之后把它挂在线段树上,就可以解决区间的问题了。 
       我想着,既然要写封装,那就彻底把它装好算了。所以说我并没有把Splay放到全局变量之中,而是用vector来实现对应的线段树节点上平衡树动态开点的问题。这就多了一些有趣的细节,但是任然可以做。又注意到vector本身空间开销就稍微大一些,所以就用了一个stack (又是STL。。。)来回收内存。为什么要用stack而不是常用的queue来实现呢? 因为我们发现操作中有一个更改操作,一般我们是用先删除后插入来解决的。如果我们能够使新插入的节点编号和旧节点一致的话,那么我们就可以省去一个重新寻找编号的过程。
       但是不幸的事情还是发生了,各大提交网站的内存限制都是在128M左右,而我的vector不知怎么的就是要170M左右的内存。。。还好学校内部的OJ上管理员开的是256M,要不然就只有50分了。所以说完全封装害人啊。。。OI的题自己搞得懂就好了。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<stack>
using namespace std;
inline int read(){
	char c;int rec=0,f=1;
	while((c=getchar())<'0'||c>'9')if(c=='-')f=-1;
	while(c>='0'&&c<='9')rec=rec*10+c-'0',c=getchar();
	return rec*f;
}
int n,m;
int c[50005];
int maxx,minn=0x3f3f3f3f;
struct Node {int F,s[2],val,size,d;};//Splay的节点
struct Splay_Tree{
	vector<Node>tree;//动态开节点
	stack<int> q;//内存回收
	int root,cnt;
	inline void Instant(){
		tree.push_back((Node){0,0,0,0x3f3f3f3f,0,0});
	        root=0;cnt=0;return ;//结构体内部(结构体中的结构体)是没有初始值为0的
	}//为了加一个没有前驱||后继的哨兵,同时也将节点的下标从1开始编号,符合平时的习惯
	inline void Pushup(int v){
		tree[v].size=tree[tree[v].s[0]].size+tree[v].d+tree[tree[v].s[1]].size;return ;
	}
	inline void Rotate(int v){
		int p=tree[v].F,g=tree[p].F;
		int t1=(v==tree[p].s[1]),t2=(p==tree[g].s[1]),S=tree[v].s[!t1];
		if(g)tree[g].s[t2]=v;tree[v].F=g;
		tree[v].s[!t1]=p;tree[p].F=v;
		tree[p].s[t1]=S;tree[S].F=p;
		Pushup(p);return ;
	}
	inline void Splay(int v){
		while(tree[v].F){
			int p=tree[v].F,g=tree[p].F;
			if(g)(v==tree[p].s[1])^(p==tree[g].s[1])?Rotate(v):Rotate(p);
			Rotate(v);
		}Pushup(v);root=v;return ;
	} 
	inline int Add_Node(Node a){
		if(!q.empty()){int p=q.top();q.pop();tree[p]=a;return p;}
		else {tree.push_back(a);cnt++;return cnt;}
	}
	inline void Insert(int x){//为了适应vector,所以和正常的数组Splay有一些区别
		Node a;a.s[0]=a.s[1]=0;a.d=1;
		a.F=0;a.size=1;a.val=x;
		if(!root){root=Add_Node(a);return ;}
		int v=root,f;
		while(v){
		    f=v;
		    if(x==tree[v].val){tree[v].d++;Splay(v);return ;}
			v=tree[v].s[x>tree[v].val];
		}
		a.F=f;v=Add_Node(a);
		tree[f].s[x>tree[f].val]=v;
		Splay(v);return ;
	}
	inline int Extreme(int v,int f){while(tree[v].s[f])v=tree[v].s[f];return v;}
	inline int Next(int v){v=tree[v].s[1];v=Extreme(v,0);return v;}
	inline int Pre(int v){v=tree[v].s[0];v=Extreme(v,1);return v;}
	inline void Delete(int v){
		Splay(v);
		if(tree[v].d>1){tree[v].d--;return ;}
		q.push(v);//将新删除的点入栈
		int L=tree[v].s[0],R=tree[v].s[1];
		if(!L&&!R){root=0;return ;}
		if(L==0){root=R;tree[R].F=0;return ;}
		if(R==0){root=L;tree[L].F=0;return ;}
		int p=L;tree[L].F=0;p=Extreme(p,1);
		Splay(p);root=p;
		tree[p].s[1]=R;tree[R].F=p;
		Pushup(v);return ;
	}
	inline int Kth(int k){
		int v=root;
		while(tree[tree[v].s[0]].size+1>k||tree[tree[v].s[0]].size+tree[v].d<k){
		    if(tree[tree[v].s[0]].size+1>k)v=tree[v].s[0];
		    else {k-=tree[tree[v].s[0]].size+tree[v].d;v=tree[v].s[1];}
	    }return v;
	}//带点数的一些操作
	inline int Find(int x){
		int v=root,rec=0;
		while(v&&tree[v].val!=x){
		    if(x>tree[v].val){rec+=tree[tree[v].s[0]].size+tree[v].d;v=tree[v].s[1];}
		    else v=tree[v].s[0];
		}
		return rec+tree[tree[v].s[0]].size;
	}
	inline int Find_pos(int x){
		int v=root;
		while(v&&tree[v].val!=x)v=tree[v].s[x>tree[v].val];
		return v;
	}
};
struct Seg_Tree{int L,R;Splay_Tree T;}tree[50005<<2];//一个线段树的节点除了区间端点之外就是一颗封装好的Splay
inline void Build(int v,int L,int R){
	tree[v].L=L;tree[v].R=R;tree[v].T.Instant();
	for(int i=L;i<=R;i++)tree[v].T.Insert(c[i]);//加点
	if(L==R)return ;
	int mid=(L+R)>>1;
	Build(v<<1,L,mid);Build(v<<1|1,mid+1,R);
	return ;
}
inline int Rank(int v,int L,int R,int x){
	if(tree[v].L>R||tree[v].R<L)return 0;
	if(tree[v].L>=L&&tree[v].R<=R){
		tree[v].T.Insert(x);
		int w=tree[v].T.Find(x);
		tree[v].T.Delete(tree[v].T.root);
		return w;
	}
	return Rank(v<<1,L,R,x)+Rank(v<<1|1,L,R,x);
}//将对应的每一颗Splay的rank值相加就是区间的rank了
inline int Pos(int x,int y,int k){
	int L=minn-1,R=maxx+1,mid;
	while(L<R){
		mid=(L+R)>>1;
		if(Rank(1,x,y,mid)<k)L=mid+1;
		else R=mid;
	}return L-1;
}//通过二分来确定第k大
inline void Change(int v,int pos,int x){
	if(tree[v].L>pos||tree[v].R<pos)return ;
	int w=tree[v].T.Find_pos(c[pos]);
	tree[v].T.Delete(w);
	tree[v].T.Insert(x);
	if(tree[v].L==tree[v].R)return ;
	Change(v<<1,pos,x);Change(v<<1|1,pos,x);
	return ;
}//单点修改,注意每一个经过的节点都要修改
inline int Pre(int v,int L,int R,int x){
	if(tree[v].L>R||tree[v].R<L)return -0x3f3f3f3f;
	if(tree[v].L>=L&&tree[v].R<=R){
		tree[v].T.Insert(x);
		int rec=tree[v].T.tree[tree[v].T.Pre(tree[v].T.root)].val;
		tree[v].T.Delete(tree[v].T.root);
		if(rec==0x3f3f3f3f)rec*=-1;//这就是没有前驱
		return rec;
	}
	return max(Pre(v<<1,L,R,x),Pre(v<<1|1,L,R,x));
}
inline int Next(int v,int L,int R,int x){
	if(tree[v].L>R||tree[v].R<L)return 0x3f3f3f3f;
	if(tree[v].L>=L&&tree[v].R<=R){
		tree[v].T.Insert(x);
		int rec=tree[v].T.tree[tree[v].T.Next(tree[v].T.root)].val;
		tree[v].T.Delete(tree[v].T.root);
		return rec;
	}//没有后继的时候哨兵的值本来就是极大值,不会造成影响
	return min(Next(v<<1,L,R,x),Next(v<<1|1,L,R,x));
}
int main(){
	n=read();m=read();
	for(int i=1;i<=n;i++){
		c[i]=read();maxx=max(maxx,c[i]);
		minn=min(minn,c[i]);
	}
	Build(1,1,n);
	for(int i=1;i<=m;i++){
		int f=read(),x=read(),y=read();
		if(f==1)cout<<Rank(1,x,y,read())+1<<'\n';
		if(f==2)cout<<Pos(x,y,read())<<'\n';
		if(f==3)Change(1,x,y),c[x]=y;
		if(f==4)cout<<Pre(1,x,y,read())<<'\n';
		if(f==5)cout<<Next(1,x,y,read())<<'\n';
	}
	return 0;
}//主函数简洁操作

 

猜你喜欢

转载自blog.csdn.net/hwzzyr/article/details/78566132