博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
平衡树(模板+文艺平衡树)
阅读量:5174 次
发布时间:2019-06-13

本文共 5185 字,大约阅读时间需要 17 分钟。

 模板存一下:求前驱后继,求x的排名和排在的x名的数,删除和插入一个数。

/*https://blog.csdn.net/clove_unique/article/details/50630280*/#include
using namespace std;#define N 100005#define INF 2100000000int f[N],ch[N][5],key[N],siz[N],cnt[N],sz=0,root=0;void clear(int x){ ch[x][1]=ch[x][0]=f[x]=cnt[x]=siz[x]=0;key[x]=-INF;} void update(int x){ if(!x) return ; siz[x]=cnt[x]; if(ch[x][0]) siz[x]+=siz[ch[x][0]];//记得是siz!! if(ch[x][1]) siz[x]+=siz[ch[x][1]];}int get(int x)//返回x是左儿子还是右儿子 { return (ch[f[x]][1]==x);}void ro(int x)//旋转操作 { int old=f[x],oldf=f[f[x]],wi=get(x);//父亲 父亲的父亲 wi判断是左儿子还是右儿子 ch[old][wi]=ch[x][wi^1]; f[ch[old][wi]]=old;//父亲的原儿子接到与x关系相反的儿子 那个儿子自己也将父亲变成它 f[old]=x; ch[x][wi^1]=old; //自己变成父亲的父亲 父亲变成方向相反的儿子 f[x]=oldf;//自己的父亲变成父亲的父亲 if(oldf) ch[oldf][ch[oldf][1]==old]=x;//父亲的父亲的儿子也变成自己 替补到old的位置上去 update(old); update(x);//先update深度大的!! }void splay(int x){ for(int fa;(fa=f[x]);ro(x)) if(f[fa])//父亲的父亲要存在 因为要旋到f[fa]的儿子 ro((get(x)==get(fa)?fa:x));//如果x与fa在一条直线上就应该先旋fa root=x;//已经把x旋转到根 就应该记录一下!! } void add(int v)//插入值为v的点 { //sz是节点的编号 if(root==0) { sz++; ch[sz][0]=ch[sz][1]=f[sz]=0; key[sz]=v; cnt[sz]=1; siz[sz]=1; root=sz; return ; } int now=root,fa=0;//从根节点开始找位置 while(1) { if(key[now]==v)//已经有了这个点 就直接在cnt上++(二叉平衡树不能有重复的点) 还要记得旋一下 { cnt[now]++; update(now); update(fa); splay(now); break; } fa=now; now=ch[now][key[now]
1) { cnt[root]--; update(root); return ; }//个数减少了记得update if(!ch[root][0]&&!ch[root][1]) { clear(root); root=0; return ; } if(!ch[root][0]) { int oldrt=root; root=ch[root][1]; f[root]=0; clear(oldrt); return ; } else if(!ch[root][1]) { int oldrt=root; root=ch[root][0]; f[root]=0; clear(oldrt); return ; } //有两个及以上的儿子 int qian=q_pre(),oldrt=root; splay(qian);//将前驱旋到根 f[ch[oldrt][1]]=root; ch[root][1]=ch[oldrt][1];//把原根的右子树接在新根上 clear(oldrt); update(root);//清理旧根 update新根 }int main(){ int n,x,op; scanf("%d",&n); while(n--) { scanf("%d%d",&op,&x); if(op==1) add(x); else if(op==2) dele(x); else if(op==3) printf("%d\n",find(x)); else if(op==4) printf("%d\n",kth(x)); else if(op==5) add(x),printf("%d\n",key[q_pre()]),dele(x); else if(op==6) add(x),printf("%d\n",key[q_hou()]),dele(x); } return 0;}/*101 1064654 11 3177211 4609291 6449851 841851 898516 819681 4927375 493598101 51 31 61 21 41 73 5*/
splay模板

题意:给一个初始序列,求经过若干次区间翻转后的序列。

思路:

维护什么?  用splay维护下标,key值存下标对应的值,如序列:1 3 4 2  则1存的key值是1,2存的key值是3,3存的key值是4,4存的key值是2.

怎么翻转?  当要翻转l和r的时候,先找到l和r这两个下标对应splay中的编号(sply节点的编号),然后将l-1旋转到根,r+1旋转到根的右儿子,由于中序遍历的有序性,[l,r]这个区间就在r+1的左子树,如此操作就实现了区间的提取。提取区间后,先对其打上一个翻转标记,当需要查询是,将翻转标记下传,一层一层地将左右子树翻转,实现区间翻转。

为什么翻转之后找rank还能找对?   这里的rank函数其实是找到下标对应splay的节点编号,而下标的中序遍历是有序的,那么通过siz的比较就可以找到对应位置

而这道题的下标和值域对应的初始值是一样的,不要弄混了。

/*洛谷P3391 【模板】文艺平衡树Splay题意:求区间翻转后的序列思路:把l-1旋到根 r+1旋到l-1的右儿子 这样r+1的左儿子就是l到r这段区间 实现了区间的提取splay的性质:中序遍历一直是有序的 所以区间的翻转就可以 */#include
using namespace std;#define inf 0x7f7f7f#define N 100005//数组不用加倍 因为平衡树一个点对应一个点 而不是一段区间 #define mid ((l+r)>>1)int ch[N][3],siz[N],key[N],fl[N],a[N],ndnum=0,root=0,fa[N];void update(int s){ siz[s]=siz[ch[s][0]]+siz[ch[s][1]]+1; }//+1因为有它自己 int build(int f,int l,int r){ if(l>r) return 0; int s=++ndnum; key[s]=a[mid]; fa[s]=f; fl[s]=0; ch[s][0]=build(s,l,mid-1);//注意与线段树的差异:s记录的是mid这个点 而它的左右儿子分别记录l~mid-1 mid+1~r ch[s][1]=build(s,mid+1,r); update(s); return s;}int get(int x){ return (ch[fa[x]][1]==x) ; }void pushdown(int x){ if(x&&fl[x]){ fl[ch[x][0]]^=1;//向左右儿子传标记 fl[ch[x][1]]^=1; swap(ch[x][0],ch[x][1]);//区间翻转 fl[x]=0; }}void rotate(int x){ int old=fa[x],oldf=fa[old],which=get(x); ch[old][which]=ch[x][which^1]; fa[ch[old][which]]=old; fa[old]=x; ch[x][which^1]=old; fa[x]=oldf; if(oldf) ch[oldf][ch[oldf][1]==old]=x; update(old),update(x);}void splay(int x,int tt)//将x旋到tt的儿子节点 { //其实这个操作旋了两次 一次是for结束时 一次是if判断后 for(int f;(f=fa[x])!=tt;rotate(x)) //判断旋两次后能不能到tt的儿子节点 如果能到 就不用旋了 直接通过for循环来旋 if(fa[fa[x]]!=tt) rotate(get(x)==get(f)?f:x);//如果在一条直线 就要先旋fa if(!tt) root=x; }int rank(int x){ int now=root; while(1){ pushdown(now);//查找排名时记得下传标记 if(x<=siz[ch[now][0]]) now=ch[now][0]; else{ x=x-siz[ch[now][0]]-1;//与线段树不同的是 它不仅要减去儿子节点 还要减去它自己 因为它自己存了mid这个点 if(!x) return now; now=ch[now][1]; } }}void turn(int l,int r){ int x=rank(l),y=rank(r+2);//因为维护的是值域平衡树 所以要先找到在平衡树中的排位(也就是说对应着哪个点) splay(x,0); splay(y,x);//把x旋到根节点(0是因为根的父亲是0) 把y旋到x的右节点 fl[ch[ch[root][1]][0]]^=1;}void dfs(int now){ pushdown(now); if(ch[now][0]) dfs(ch[now][0]); if(key[now]!=-inf&&key[now]!=inf) printf("%d ",key[now]);//注意是key里面存储真正的值 而now只是平衡树的节点 if(ch[now][1]) dfs(ch[now][1]);}int main(){ int n,m,l,r; scanf("%d%d",&n,&m); for(int i=2;i<=n+1;i++) a[i]=i-1; a[1]=-inf; a[n+2]=inf; root=build(0,1,n+2);//根是0 注意是1 到 n+2 因为加了正无穷和负无穷 while(m--){ scanf("%d%d",&l,&r); turn(l,r); } dfs(root);}/*5 31 31 31 4*/
文艺平衡树

 

转载于:https://www.cnblogs.com/mowanying/p/11247033.html

你可能感兴趣的文章
Beta总结
查看>>
Spring.NET学习笔记
查看>>
python基础小练习
查看>>
Spring杂记BeanFactory之getBean方法
查看>>
linux 下 tcpdump 命令详解
查看>>
在阿里云搭建属于自己的个人空间--让全世界找到我
查看>>
每日编程-20170315
查看>>
K-MEANS算法
查看>>
翻译:Hilo:第一章 介绍 Hilo
查看>>
OpenCV-自适应阈值化
查看>>
BestCoder-Round#33
查看>>
Codeforces Round #527 (Div. 3) D1. Great Vova Wall (Version 1)
查看>>
集合,ArrayList练习
查看>>
宋体汉字字号和点阵大小对应关系
查看>>
动力学公式…
查看>>
[BZOJ1500][NOI2005]维修数列
查看>>
[OJ#15]TR #2 画心
查看>>
吴恩达机器学习笔记 —— 13 支持向量机
查看>>
k-means算法的优缺点以及改进
查看>>
Spring-Boot + MyBatis-Plus 踩坑记录
查看>>