1、Splay树------------------代码讲解都来自董老师,讲的非常非常清楚
复杂度都为O(NlogN)
特点:
(1)允许任意节点旋转到根(经常查询或使用这个数)
(2)当需要分裂和合并的时候非常方便
操作:
1、旋转:分为左右旋,改变三条边
2、提根splay
根据x的位置,可以分为3种类型
1)x的父节点就是根,旋转一次就可以
2)x的父节点,的父节点,三点共线:先旋转x的父节点,在旋转x
3)x的父节点,的父节点,三点不共线:把x按不同的方向旋转2次
3、查找:找到某个节点,然后splay到根节点
4 and 5、查找某个节点的前驱、后继:把这个节点查找并旋转到根之后,沿着左节点一直往右走找前驱、或者右节点往左右找后继
6、删除:通过找到这个节点的前驱和后继,把他甩到根节点上去,当然要判断这个节点的出现次数做不同的操作
7、查询数v的排名
8、查询排名为k的节点
https://www.luogu.com.cn/problem/P3369 P3369 【模板】普通平衡树
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=100010; const int INF=0x3fffffff; typedef long long LL; #define lson(x) tr[x].s[0] #define rson(x) tr[x].s[1] int n,m; int root,idx; struct node{ int s[2]; int p; //父亲 int v;//节点权值 int cnt;//权值出现次数 int siz;//子树大小 void init(int p1,int v1){ p=p1;v=v1;cnt=siz=1; } }tr[maxn]; void pushup(int x){ tr[x].siz=tr[lson(x)].siz+tr[rson(x)].siz+tr[x].cnt; } void rotat(int x){ //旋转 int y=tr[x].p,z=tr[y].p; //爸爸、祖父 int k=tr[y].s[1]==x; tr[z].s[tr[z].s[1]==y]=x; //把儿子和祖父连起来 tr[x].p=z; tr[y].s[k]=tr[x].s[k^1]; //把儿子的另一个节点换给爸爸 tr[tr[x].s[k^1]].p=y; tr[x].s[k^1]=y; //把爸爸连到儿子底下 tr[y].p=x; pushup(y); //先更新底下的 pushup(x); //在更新上面的 } void splay(int x,int k){ //把x转到k底下,k=0就是转到根底下 while(tr[x].p!=k){ int y=tr[x].p,z=tr[y].p; if(z!=k) (lson(y)==x)^(lson(z)==y)? rotat(x):rotat(y); //直线型或弯曲 rotat(x); } if(k==0) root=x; } void inser(int v){ //插入 int x=root,p=0; while(x&&tr[x].v!=v){ p=x;x=tr[x].s[v>tr[x].v]; } if(x) tr[x].cnt++; else{ x=++idx; tr[p].s[v>tr[p].v]=x; tr[x].init(p,v); } splay(x,0); } void findd(int v){ //找到v并转到根 int x=root; while(tr[x].s[v>tr[x].v]&&v!=tr[x].v) x=tr[x].s[v>tr[x].v]; splay(x,0); } int getpre(int v){//前驱 findd(v); int x=root; if(tr[x].v<v) return x; //不存在也可以找到前驱 x=lson(x); while(rson(x)) x=rson(x); return x; } int getnex(int v){//后继 findd(v); int x=root; if(tr[x].v>v) return x; x=rson(x); while(lson(x)) x=lson(x); return x; } void del(int v){ //删除 int pre=getpre(v); int suc=getnex(v); splay(pre,0); splay(suc,pre); //把节点弄成叶子结点 int dell=tr[suc].s[0]; if(tr[dell].cnt>1) { tr[dell].cnt--; splay(dell,0); } else { tr[suc].s[0]=0; splay(suc,0); } } int getrank(int v){ //排名 findd(v); return tr[tr[root].s[0]].siz; //因为还有个负无穷,所以不加1 } int getval(int k){ //排第k的 int x=root; while(1){ int y=lson(x); if(tr[y].siz+tr[x].cnt<k){ k-=tr[y].siz+tr[x].cnt; x=rson(x); //要往右子树找 } else if(tr[y].siz>=k) x=y; else break; //找到了,因为左右子树都不能走了 } splay(x,0); return tr[x].v; } int main(){ inser(-INF); inser(INF); //塞这个进去的目的是确保都找得到前驱、后继 scanf("%d",&n); while(n--){ int op,x; scanf("%d %d",&op,&x); if(op==1) inser(x); if(op==2) del(x); if(op==3) printf("%d\n",getrank(x)); if(op==4) printf("%d\n",getval(x+1)); if(op==5) printf("%d\n",tr[getpre(x)].v); if(op==6) printf("%d\n",tr[getnex(x)].v); } return 0; }
hdu 1890(这个不是裸体,只是利用了旋转功能
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=101000; const int INF=0x3fffffff; typedef long long LL; //建树: //用Splay旋转到根,其左子树的大小即排在左边的个数 ,输出就可以 //翻转左子树:用了线段树的思想:进行标记,而不是直接操作,等splay操作的时候在处理 //标记函数:updat_rev(), //删除根:根据标记进行子树的翻转 int rev[maxn]; //标记i被翻转 int pre[maxn]; //i的父节点 int size[maxn]; //i的子树上节点的个数 int tree[maxn][2] ; //记录树,0左,1右 int root; struct node{ int val,id; bool operator < (const node &a)const{ if(val==a.val) return id<a.id; else return val<a.val; } }nodes[maxn]; void push_up(int x){ //记录以x为根的子树包含的节点 size[x]=size[tree[x][0]]+size[tree[x][1]]+1; } //进行旋转 void updata_rev(int x){ if(!x) return; //NULL swap(tree[x][0],tree[x][1]); rev[x]^=1; //记录已经更新了 } void pushdown(int x){ if(rev[x]){ //如果标记了,就旋转,一般用在 rotate和splay updata_rev(tree[x][0]); updata_rev(tree[x][1]); rev[x]=0; } } void Rotate(int x,int c){ //c=0左旋, c=1右旋 int y=pre[x]; pushdown(y); pushdown(x); //先更新 //然后就是更改和更新信息 //1、改变父子结构 tree[y][!c]=tree[x][c]; //y的儿子 pre[tree[x][c]]=y; //x的儿子重新认爸爸 if(pre[y]){ tree[pre[y]][tree[pre[y]][1]==y]=x; //改变儿子,y的爸爸 //原来是哪边,现在还是哪边 } pre[x]=pre[y]; //x也重新仁爸爸 tree[x][c]=y; //儿子 pre[y]=x; push_up(y); } void splay(int x,int goal){ //把节点x作为goal的孩子,如果goal为0,那么就是旋转到跟 pushdown(x); while(pre[x]!=goal){ //一直旋转,知道成为goal的孩子 //区分三种情况 if(pre[pre[x]]==goal){ //1、父节点是根,直接旋转一次就可以 pushdown(pre[x]); pushdown(x); Rotate(x,tree[pre[x]][0]==x); //左孩子:右旋,右孩子:左旋 } else{ //父节点不是根 pushdown(pre[pre[x]]); pushdown(pre[x]); //先更新 pushdown(x); int y=pre[x]; int c=(tree[pre[y]][0]==y); //先判断父节点是左还是右孩子 if(tree[y][c]==x){ //三点不共线!!!! 把x按不同的方向旋转2次 Rotate(x,!c); Rotate(x,c); } else{ //三点共线 先旋转x的父节点,在旋转x Rotate(y,c); Rotate(x,c); } } } push_up(x); if(goal==0) root=x; //如果goal是0,那么就更新根节点 } //接下来是删除节点操作 int get_max(int x){ pushdown(x); //更新 while(tree[x][1]) { x=tree[x][1]; //找前驱:一直在做孩子的右节点找:BST pushdown(x); } return x; } void del_node(){ //删除根节点(通过前面的操作,已经把需要删除的节点放在根节点了 if(tree[root][0]==0){ //没有左孩子:没有前驱,而且可以直接删掉 root=tree[root][1]; pre[root]=0; } else{ int m=get_max(tree[root][0]); //找到前驱 splay(m,root); //提根 tree[m][1]=tree[root][1]; //右孩子=另一棵树(相当于合并两棵树 pre[tree[root][1]]=m; //更新爸爸 root=m; pre[root]=0; push_up(root); } } void newnode(int &x,int fa,int val){ //新建节点 x=val; pre[x]=fa; size[x]=1; rev[x]=0; tree[x][0]=tree[x][1]=0; //全体初始化 } void buildtree(int &x,int l,int r,int fa){ //建树 if(l>r) return; int mid=(l+r)>>1; //中间开始建(平衡) newnode(x,fa,mid); //先按照初始位置建树!!! buildtree(tree[x][0],l,mid-1,x); buildtree(tree[x][1],mid+1,r,x); push_up(x); } void inti(int n){ root=0; tree[root][0]=tree[root][1]=pre[root]=size[root]=0; buildtree(root,1,n,0); } int main(){ int n; while(~scanf("%d",&n)&&n){ inti(n); for(int i=1;i<=n;i++){ scanf("%d",&nodes[i].val); nodes[i].id=i; } sort(nodes+1,nodes+1+n); for(int i=1;i<n;i++){ //只进行n-1次操作 splay(nodes[i].id,0); //第i次旋转:把第i大的节点旋转到根 updata_rev(tree[root][0]); //左子树需要旋转 printf("%d ",i+size[tree[root][0]]); //第i个被翻转的数的左边的数,就是左子树的个数 del_node(); } printf("%d\n",n); } return 0; }
文艺平衡树
1、注意上传pushup和下传pushdown 的时机
2、夹挤区间的技巧
3、注意::::使用了线段树的技巧:tag标记,而且这个题目很合适,重复翻转等于不翻转,等到遇到了再去做翻转
https://www.luogu.com.cn/problem/P3391 P3391 【模板】文艺平衡树
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=100010; const int INF=0x3fffffff; int n,m; struct node{ int s[2],p,v; int siz,tag; //有了懒标记 void init(int p1,int v1){ p=p1;v=v1;siz=1; } }tr[maxn]; int root,idx; void pushup(int x){ tr[x].siz=tr[tr[x].s[0]].siz+tr[tr[x].s[1]].siz+1; } void pushdown(int x){ if(tr[x].tag){ //下传标记 swap(tr[x].s[0],tr[x].s[1]); tr[tr[x].s[0]].tag^=1; tr[tr[x].s[1]].tag^=1; tr[x].tag=0; } } void rotat(int x){ int y=tr[x].p, z=tr[y].p; int k = tr[y].s[1]==x; tr[z].s[tr[z].s[1]==y] =x; tr[x].p = z; tr[y].s[k] = tr[x].s[k^1]; tr[tr[x].s[k^1]].p = y; tr[x].s[k^1] = y; tr[y].p = x; pushup(y);pushup(x); } void splay(int x,int k){ while(tr[x].p!=k){ int y=tr[x].p, z=tr[y].p; if(z!=k) // 折转底,直转中 (tr[y].s[0]==x)^(tr[z].s[0]==y)? rotat(x) : rotat(y); rotat(x); } if(k==0) root=x; } void inser(int v){ int x=root,p=0; while(x){ p=x; x=tr[x].s[v>tr[x].v]; } x=++idx; tr[p].s[v>tr[p].v]=x; tr[x].init(p,v); splay(x,0); } int get_k(int k){ int x=root; while(1){ pushdown(x); int y=tr[x].s[0]; if(tr[y].siz+1<k){ k -= tr[y].siz+1; x = tr[x].s[1]; } else if(tr[y].siz>=k) x=y; else return x; } } void output(int x){ pushdown(x); //需要往下更新的时候 if(tr[x].s[0]) output(tr[x].s[0]); if(tr[x].v>=1&&tr[x].v<=n) printf("%d ",tr[x].v); //过滤最大值最小值 if(tr[x].s[1]) output(tr[x].s[1]); } int main(){ inser(-INF); inser(INF); scanf("%d %d",&n,&m); for(int i=1;i<=n;i++) inser(i); while(m--){ int l,r; scanf("%d %d",&l,&r); l=get_k(l); r=get_k(r+2); //因为有最小值,所有往右偏移1 splay(l,0);// 把[l,r]夹挤到l-1和r+1之间 splay(r,l); tr[tr[r].s[0]].tag^=1; } output(root); return 0; }