普通、文艺以及二逼平衡树
平衡树的题。
这种数据结构的裸题,就是拿来虐我这种巨菜的(尤其是代码能力不强的同学)。
首先说明,这三道题我都是用Splay写的,想要看Treap的同学可以出门右转了。
那么先说第一道,普通平衡树。
普通平衡树
Description
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
1. 插入x数
2. 删除x数(若有多个相同的数,因只删除一个)
3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
4. 查询排名为x的数
5. 求x的前驱(前驱定义为小于x,且最大的数)
6. 求x的后继(后继定义为大于x,且最小的数)
1. 插入x数
2. 删除x数(若有多个相同的数,因只删除一个)
3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
4. 查询排名为x的数
5. 求x的前驱(前驱定义为小于x,且最大的数)
6. 求x的后继(后继定义为大于x,且最小的数)
Input
第一行为n,表示操作的个数
下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6)
下面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
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
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
接下来m行每行两个数[L,R] 数据保证 1<=L<=R<=n
Output
输出一行n个数字,表示原始序列经过m次变换后的结果
Sample Input
5 3
1 3
1 3
1 4
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,且最小的数)。
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 的后继。
第二行有 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
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
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;
}//主函数简洁操作