模板:二叉搜索树平衡树

文章目录

  • 前言
  • 二叉搜索树
    • 代码
  • treap
    • 代码
  • splay
    • 开点
    • 旋转
    • splay
    • 插入
    • 查找第k大元素
    • 查找给定元素的排名
    • 前驱&后继
    • 删除
    • 完整代码
  • 练习总结

前言

终于开始学这个东西了
看了好几篇博客才找到一篇可读的qwq
我曾经还以为线段树码量大…我真傻,真的
所谓平衡树,就是把二叉搜索树加了一个随机权值
并通过旋转使这个权值始终符合堆的性质
(treap=tree+heap)
我觉得平衡树主要的功能就是维护排名相关的东西
(update:更正观点!平衡树最好用的地方还是区间问题,排名问题在序列上可以主席树,动态的可以树状数组,为啥要写splay这种东西…)
前驱后继这些其实都可以直接拿set偷懒
(当然本刚学treap1h的蒟蒻的理解完全没有参考价值)
一开始WA成了60分qwq
千万注意一定不要落掉无处不在的pushup!

二叉搜索树

不学BST,何以treap? ——鲁迅

二叉搜索树是一种二叉树的树形数据结构,其定义如下:

  1. 空树是二叉搜索树。

  2. 若二叉搜索树的左子树不为空,则其左子树上所有点的附加权值均小于其根节点的值。

  3. 若二叉搜索树的右子树不为空,则其右子树上所有点的附加权值均大于其根节点的值。

  4. 二叉搜索树的左右子树均为二叉搜索树。

二叉搜索树上的基本操作所花费的时间与这棵树的高度成正比。对于一个有 n个结点的二叉搜索树中,这些操作的最优时间复杂度为 Ologn,最坏为On。随机构造这样一棵二叉搜索树的期望高度为logn。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+100;
#define ll long long
int n,m,k;
int x,y;
int cnt[N],ls[N],rs[N],val[N],siz[N],tot=1;
void insert(int &o,int v){//插入元素if(!o){o=++tot;val[o]=v;ls[o]=rs[o]=0;siz[o]=cnt[o]=1;}siz[o]++;if(val[o]==v) {cnt[o]++;return;}if(v<val[o]) insert(ls[o],v);else insert(rs[o],v);
}
int delmin(int &o){if(!ls[o]){int u=o;o=rs[o];return u;}else{int u=delmin(ls[o]);siz[o]-=cnt[u];return u;}
}
void del(int &o,int v){//删除元素siz[o]--;if(val[o]==v){if(cnt[o]>1) cnt[o]--;else if(ls[o]&&rs[o]) o=delmin(rs[o]);else o=ls[o]+rs[o];return;}if(v<val[o]) del(ls[o],v);else del(rs[o],v);
}
int askrank(int o,int v){//查询x的排名if(val[o]==v) return siz[ls[o]]+1;else if(val[o]>v) return askrank(ls[o],v);else return siz[ls[o]]+cnt[o]+askrank(rs[o],v);
}
int asknth(int o,int k){//查询第k大的元素if(siz[ls[o]]>=k) return asknth(ls[o],k);else if(siz[ls[o]]+cnt[o]>=k) return val[o];else return asknth(rs[o],k-(siz[ls[o]]+cnt[o]));
}
int main(){scanf("%d",&n);int flag;val[1]=-2e9;int r=1;for(int i=1;i<=n;i++){scanf("%d%d",&flag,&x);if(flag==1) insert(r,x);else if(flag==2) del(r,x);else if(flag==3) printf("%d\n",askrank(1,x));else if(flag==4) printf("%d\n",asknth(1,x));}return 0;
}
/**/ 

treap

旋转是平衡树的灵魂

一个很重要的技巧是利用0/1存储左右儿子
这样在旋转的时候写起来会容易很多

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+2e5+100;
#define ll long long
int n,m,k;
int x,y;
int cnt[N],ch[N][2],val[N],siz[N],tot,r,dat[N];
int New(int v){val[++tot]=v;dat[tot]=rand();ch[tot][0]=ch[tot][1]=0;siz[tot]=cnt[tot]=1;return tot;
}
void pushup(int o){siz[o]=siz[ch[o][0]]+siz[ch[o][1]]+cnt[o];
}
void build(){r=New(-2e9);ch[1][1]=New(2e9);pushup(r);
}
void rotate(int &o,int d){int temp=ch[o][!d];ch[o][!d]=ch[temp][d];ch[temp][d]=o;o=temp;pushup(o);pushup(ch[o][d]);
}
void insert(int &o,int v){if(!o){o=New(v);return;}if(v==val[o]){cnt[o]++;pushup(o);return;}int d= v>val[o];insert(ch[o][d],v);if(dat[ch[o][d]]>dat[o]) rotate(o,!d);pushup(o);
}
void del(int &o,int v){if(!o) return;if(v==val[o]){if(cnt[o]>1){cnt[o]--;pushup(o);return;}if(ch[o][0]||ch[o][1]){int d=!ch[o][1]||dat[ch[o][1]]<dat[ch[o][0]];rotate(o,d);del(ch[o][d],v);pushup(o);}else o=0;return;}if(v<val[o]) del(ch[o][0],v);else del(ch[o][1],v);pushup(o);
}
int getrank(int o,int v){if(!o) return 1;if(val[o]==v) return siz[ch[o][0]]+1;else if(v<val[o]) return getrank(ch[o][0],v);else return getrank(ch[o][1],v)+siz[ch[o][0]]+cnt[o];
}
int getnth(int o,int k){if(!o) return 2e9;if(siz[ch[o][0]]>=k) return getnth(ch[o][0],k);else if(siz[ch[o][0]]+cnt[o]>=k) return val[o];else return getnth(ch[o][1],k-(siz[ch[o][0]]+cnt[o]));
}
int getpre(int v){int res=-2e9,p=r;while(p){if(val[p]<v){res=val[p];p=ch[p][1];}else p=ch[p][0];}return res;
}
int getnxt(int v){int res=2e9,p=r;while(p){if(val[p]>v){res=val[p];p=ch[p][0];}else p=ch[p][1];}return res;
}
int main(){scanf("%d%d",&n,&m);build();int flag;for(int i=1;i<=n;i++){scanf("%d",&x);insert(r,x);}int ans=0,lst=0;for(int i=1;i<=m;i++){scanf("%d%d",&flag,&x);x^=lst;if(flag==1) insert(r,x);else if(flag==2) del(r,x);else if(flag==3){int res=getrank(r,x)-1;lst=res;ans^=res;}else if(flag==4){int res=getnth(r,x+1);lst=res;ans^=res;}else if(flag==5){int res=getpre(x);lst=res;ans^=res;}else{int res=getnxt(x);lst=res;ans^=res;}}printf("%d\n",ans);return 0;
}
/**/ 

splay

看好几篇博客说splay在区间问题的功能更强大,所以也学习了splay
最后实在de不出来bug还是动用了减法原理
累死窝了qwq
这个东西真的好难debug
但是决定以后就用它了awa
当然要用强的啦
这个东西好好讲讲

开点

所有的点都是开出来的

开点还是比较正常

int New(int v,int fa){val[++tot]=v;f[tot]=fa;ch[tot][0]=ch[tot][1]=0;siz[tot]=cnt[tot]=1;return tot;
}

旋转

它也是旋转完成的,不能没有它
和treap一样啦
防止写错,总体的改变可以分三对

  1. x与gfa的父子关系
  2. fa与x的父子关系
  3. x的异向儿子与fa的父子关系
void rotate(int x){int fa=f[x],gfa=f[fa];int k=getwhich(x);int temp=ch[x][k^1];f[temp]=fa;ch[fa][k]=temp;f[x]=gfa;if(gfa) ch[gfa][ch[gfa][1]==fa]=x;f[fa]=x;ch[x][k^1]=fa;pushup(x);pushup(fa);
}

splay

splay怎么能不splay呢
所以我们现在讲讲splay的splay部分(停止扯淡)
splay总的来说就是把一个结点不停转转转
一直转到根的地方
沿途长链死光光
从而保证复杂度的正确性
这也是splay的精髓所在
而且跳到根也便利了我们其他的操作

有一个很关键的细节
就是当父亲和自己相对于各自父节点的方向同向时
必须要先转父亲
不然就无法达到消链的目的
这个可以自己画画图理解
(我看别人题解画的天花乱坠,最后还是自己画图才明白的)
代码极为简洁

void splay(int x){for(int fa;fa=f[x];rotate(x)){if(f[fa]) rotate((getwhich(fa)==getwhich(x))?fa:x);}r=x;
}

插入

开始干正事了
找到应该加点的位置开点
然后splay一下
注意pushup!

void insert(int v){if(!r){r=New(v,0);return;}int now=r,fa=0;while(1){if(val[now]==v){cnt[now]++;pushup(now);pushup(fa);splay(now);break;}fa=now;now=ch[now][v>val[now]];if(!now){ch[fa][v>val[fa]]=New(v,fa);pushup(fa);
//			printf("\ninsert:(pre)\n");
//			print();splay(tot);
//			printf("\ninsert:(after)\n");
//			print();break;}}
}

查找第k大元素

这个很好写
理解起来应该也不难

int findnth(int k){int now=r;while(1){if(siz[ch[now][0]]>=k) now=ch[now][0];else if(siz[ch[now][0]]+cnt[now]>=k) return val[now];else{k-=siz[ch[now][0]]+cnt[now];now=ch[now][1];}}
}

查找给定元素的排名

这个也没有太大的难度
(尽管我de了一年多之后发现就是这里写挂的)
为了后面删除元素的遍历我们找到这个元素后splay一下

int findrank(int x){int now=r,ans=0;while(1){if(!now) return ans+1;if(val[now]>x) now=ch[now][0];else if(val[now]==x){ans+=siz[ch[now][0]];//记录一下车祸现场splay(now);return ans+1;}else{ans+=siz[ch[now][0]]+cnt[now];now=ch[now][1];}}
}

前驱&后继

这里是找的根的前驱(后继)
找给定值的前驱(后继)的话就先insert进去,它自动splay到根,然后再求就行了

int findpre(){int now=ch[r][0];while(ch[now][1]) now=ch[now][1];return now;
}
int findnxt(){int now=ch[r][1];while(ch[now][0]) now=ch[now][0];return now;
}

删除

这个是重点
首先把删除的元素利用前面现成的findrank提到根上
有副本就直接删
否则看它的儿子情况
啥都没有就直接变空树了
只有一个就把那个儿子当成根
如果两个儿子都有就考虑把根的前驱提上来
因为是前驱,所以它在到x之前一定没有右儿子
也就是这样:
请添加图片描述

再转一下:
请添加图片描述

注意到待删元素一定没有左儿子
因此我们可以把B直接接到pre上达到删除的目的
也就是:
请添加图片描述

这样就ok啦

void del(int v){findrank(v);if(cnt[r]>1) {cnt[r]--;return;}else if(!ch[r][0]&&!ch[r][1]){r=0;return;}else if(!ch[r][0]){int temp=r;r=ch[r][1];f[r]=0;return;}else if(!ch[r][1]){int temp=r;r=ch[r][0];f[r]=0;return;}int temp=ch[r][1],pre=findpre(),oldr=r;splay(pre);ch[r][1]=temp;f[temp]=r;pushup(r);
}

完整代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+100;
#define ll long long
int n,m,k;
int x,y;
int cnt[N],ch[N][2],val[N],siz[N],tot,r;
int f[N];
int New(int v,int fa){val[++tot]=v;f[tot]=fa;ch[tot][0]=ch[tot][1]=0;siz[tot]=cnt[tot]=1;return tot;
}
void pushup(int o){if(o) siz[o]=siz[ch[o][0]]+siz[ch[o][1]]+cnt[o];
}
void build(){r=New(-2e9,0);ch[1][1]=New(2e9,r);pushup(r);
}
int getwhich(int x){return ch[f[x]][1]==x;
}
void rotate(int x){int fa=f[x],gfa=f[fa];int k=getwhich(x);int temp=ch[x][k^1];f[temp]=fa;ch[fa][k]=temp;f[x]=gfa;if(gfa) ch[gfa][ch[gfa][1]==fa]=x;f[fa]=x;ch[x][k^1]=fa;pushup(x);pushup(fa);
}
void splay(int x){for(int fa;fa=f[x];rotate(x)){if(f[fa]) rotate((getwhich(fa)==getwhich(x))?fa:x);}r=x;
}
void insert(int v){if(!r){r=New(v,0);return;}int now=r,fa=0;while(1){if(val[now]==v){cnt[now]++;pushup(now);pushup(fa);splay(now);break;}fa=now;now=ch[now][v>val[now]];if(!now){ch[fa][v>val[fa]]=New(v,fa);pushup(fa);
//			printf("\ninsert:(pre)\n");
//			print();splay(tot);
//			printf("\ninsert:(after)\n");
//			print();break;}}
}
int findnth(int k){int now=r;while(1){if(siz[ch[now][0]]>=k) now=ch[now][0];else if(siz[ch[now][0]]+cnt[now]>=k) return val[now];else{k-=siz[ch[now][0]]+cnt[now];now=ch[now][1];}}
}
int findrank(int x){int now=r,ans=0;while(1){if(!now) return ans+1;if(val[now]>x) now=ch[now][0];else if(val[now]==x){ans+=siz[ch[now][0]];splay(now);return ans+1;}else{ans+=siz[ch[now][0]]+cnt[now];now=ch[now][1];}}
}
int findpre(){int now=ch[r][0];while(ch[now][1]) now=ch[now][1];return now;
}
int findnxt(){int now=ch[r][1];while(ch[now][0]) now=ch[now][0];return now;
}
void del(int v){findrank(v);if(cnt[r]>1) {cnt[r]--;return;}else if(!ch[r][0]&&!ch[r][1]){r=0;return;}else if(!ch[r][0]){int temp=r;r=ch[r][1];f[r]=0;return;}else if(!ch[r][1]){int temp=r;r=ch[r][0];f[r]=0;return;}int temp=ch[r][1],pre=findpre(),oldr=r;splay(pre);ch[r][1]=temp;f[temp]=r;pushup(r);
}
int main(){scanf("%d",&n);int flag;for(int i=1;i<=n;i++){scanf("%d%d",&flag,&x);if(flag==1) insert(x);else if(flag==2) del(x);else if(flag==3) printf("%d\n",findrank(x));else if(flag==4) printf("%d\n",findnth(x));else if(flag==5){insert(x);printf("%d\n",val[findpre()]);del(x);}else{insert(x);printf("%d\n",val[findnxt()]);del(x);}
//		print();}return 0;
}
/**/ 

练习总结

传送门

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/318197.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

51nod2626-未来常数【树上启发式合并,线段树】

正题 题目链接:http://www.51nod.com/Challenge/Problem.html#problemId2626 题目大意 给出nnn个点的一棵树&#xff0c;每个区间[l,r][l,r][l,r]的代价是选出这个区间中的一个点xxx使得它走到所有点然后又回到xxx的路程最短长度&#xff0c;求一个随机区间的期望代价。 1≤n…

A*,IDA*—高档次的暴搜

A*通过评价函数来判断当前状态是否可以到达最终状态(即可行性剪枝)&#xff0c;来减少不必要的搜索。 例题——P2324 [SCOI2005]骑士精神 我们通过当前不在指定位置上的棋子个数为评价函数&#xff0c;\(used\) 【评价函数值】超过了预期的值&#xff0c;便不用再线下深入搜索了…

P2486 [SDOI2011]染色

P2486 [SDOI2011]染色 题意&#xff1a; 题解&#xff1a; 与一般的树链剖分相比&#xff0c;不同点在于查询的不是路径上颜色的数量而是颜色段的数量 对于两个颜色段&#xff0c;112和221&#xff0c;两个颜色段数量都是2 如果合在一起颜色段的数量就是3&#xff0c;因为左边…

牛客网CSP-S提高组赛前集训营1题解(仓鼠的石子游戏 [博弈论] + 乃爱与城市的拥挤程度 [树上DP] + 小w的魔术扑克[dfs + 离线])

文章目录T1&#xff1a;仓鼠的石子游戏题目题解代码实现T2&#xff1a;乃爱与城市拥挤程度题目题解代码实现T3&#xff1a;小w的魔术扑克题目题解代码实现T1&#xff1a;仓鼠的石子游戏 题目 仓鼠和兔子被禁止玩电脑&#xff0c;无聊的他们跑到一块空地上&#xff0c;空地上有…

使用PerfView监测.NET程序性能(二):Perfview的使用

在上一篇博客使用PerfView监测.NET程序性能&#xff08;一&#xff09;&#xff1a;Event Trace for Windows 中&#xff0c;我们了解了对Windows及应用程序进行性能分析的基础&#xff1a;Event Trace for Windows (ETW)。现在来看看基于ETW的性能分析工具——Perfview.exePer…

连通性相关

强联通分量 强连通&#xff1a;有向图 \(G\) 强连通表示&#xff0c;\(G\) 中任意两个结点连通。 强连通分量( Strongly Connected Components &#xff0c;简称 \(\operatorname{SCC}\) )&#xff1a;极大的 强连通子图。 Tarjan 维护了以下两个变量&#xff1a; \(dfn\) &…

CF505E-Mr. Kitayuta vs. Bamboos【贪心,二分】

正题 题目链接:https://www.luogu.com.cn/problem/CF505E 题目大意 开始一个长度为nnn的序列hhh&#xff0c;mmm天每天你可以kkk次选择一个hih_ihi​让它等于himax{hi−p,0}h_imax\{h_i-p,0\}hi​max{hi​−p,0}&#xff0c;然后结束时让每个hihiaih_ih_ia_ihi​hi​ai​&…

阶段总结:8.09-8.18 十日模拟

一图流了解一下 文章目录十日谈总结十日谈 再总结一下 8.09 搜索模拟&#xff1a;25分。…毕竟是第一天不太适应吧 &#xff08;拼命找借口&#xff09;。没有看到标题就很淦&#xff0c;就是全写挂了而已&#xff0c;已经无从谈起…hzwer的粉丝那题提醒我们不要被吓人的数据…

学习有向图和无向图的强连通分量(基本概念+割点+点双联通分量+桥+边双连通分量+全套模板【Tarjan】)

最近总是考到Tarjan&#xff0c;让我措手不及基本概念割点以及点双连通分量Tarjan法求割点推导过程代码实现Tarjan法求点双连通分量推导过程代码实现有向图的Tarjan缩点桥与边双连通分量Tarjan法求桥理论推导代码实现Tarjan法求边双连通分量理论推导代码实现前言&#xff1a;有…

.NET Core下的Spring Cloud——前言和概述

前言前几年一直在写类似dubbo&#xff0c;Spring Cloud的微服务框架辗辗转转重复了多次&#xff0c;也重构推翻了很多次&#xff0c;其中诞生了“Rabbit.Rpc”,”Go”,”RabbitCloud”等开源项目。其中不乏他人对这些项目的完善。很高兴自己的开源项目能够给他人提供思路和复用…

期望 概率DP

期望 \(x\) 的期望 \(E(x)\) 表示平均情况下 \(x\) 的值。 令 \(C\) 表示常数&#xff0c; \(X\) 和 \(Y\) 表示两个随机变量。 \(E(C)C\) \(E(C \times X)C \times E(X)\) \(E(XY)E(X)E(Y)\) 期望的线性性 \(E(XY)\) 不一定等于 \(E(X) \times E(Y)\) 期望练习&#xff1a…

CF785E Anton and Permutation

CF785E Anton and Permutation 题意&#xff1a; 对于一个长度为 n 的序列进行 k 次操作&#xff0c;每次操作都是交换序列中的某两个数。对于每一个操作&#xff0c;回答当前序列中有多少个逆序对。 1<n<200000 1<q<50000 题解&#xff1a; 动态逆序对&#x…

P5311-[Ynoi2011]成都七中【点分树,树状数组】

正题 题目链接:https://www.luogu.com.cn/problem/P5311 题目大意 给出nnn个点的一棵树&#xff0c;每个节点有一个颜色&#xff0c;mmm次询问提出区间[l,r][l,r][l,r]的点构成的生成子图中xxx所在连通块的颜色数。 1≤n,m,ai≤1051\leq n,m,a_i\leq 10^51≤n,m,ai​≤105 解…

[ NOIP提高组 2016]愤怒的小鸟(暴搜 + 状压DP)// [SNOI2017]一个简单的询问(莫队)

一次性写两道题T1&#xff1a;一个简单的询问题目题解代码实现T2&#xff1a;愤怒的小鸟题目暴搜题解暴搜代码实现状压DP题解状压DP代码实现T1&#xff1a;一个简单的询问 题目 给你一个长度为 N 的序列 ai ,1≤i≤N&#xff0c;和 q 组询问&#xff0c;每组询问读入 l1,r1,l…

微软发布新的 Azure Pipelines 功能和集成

在最近举行的Connect()大会上&#xff0c;微软发布了几项新功能以及与 Azure Pipelines 的集成&#xff0c;包括 Visual Studio Code 的 Azure Pipelines 扩展、GitHub 版本管理、对 IoT 项目的支持以及 ServiceNow 集成。自从 9 月份推出 Azure Pipelines 以来&#xff0c;这种…

平衡树练习总结

文章目录前言普通平衡树文艺平衡树郁闷的出纳员书架宠物收养场机械排序千山鸟飞绝总结前言 专门刷了一天半的平衡树 &#xff08;附带划水OvO&#xff09; 使用的是蜜汁常数的splay 感觉对平衡树的理解还是有帮助的 一些较为常规的平衡树的题应该是差不多了 更正之前刚学的观点…

二维树状数组

二维树状数组可以实现在平面上的区域加、区域查询等操作。 区域修改 我们在一维时维护树状数组的区间操作时&#xff0c;对其进行了差分。类比一维的思想&#xff0c;我们在二维平面上也对树状数组差分。 我们来看二维的前缀和&#xff1a; \[sum(i,j)sum(i-1,j)sum(i,j-1)-sum…

【AcWing 249. 蒲公英】

【AcWing 249. 蒲公英】 题意&#xff1a; 长度为n的序列&#xff0c;给定区间&#xff0c;求区间众数&#xff0c;如果出现次数相同&#xff0c;输出编号最小的 题解&#xff1a; 区间众数&#xff0c;不带修改&#xff0c;强制在线&#xff08;否则可以莫队&#xff09; …

年末展望:Oracle 对 JDK收费和.NET Core 给我们的机遇

2018年就结束了&#xff0c;马上就要迎来2019年&#xff0c;这一年很不平凡&#xff0c;中美贸易战还在继续&#xff0c;IT互联网发生急剧变化&#xff0c;大量互联网公司开始裁员&#xff0c;微软的市值在不断上升 &#xff0c;在互联网公司的市值下跌过程中爬到了第一的位置&…

等比数列三角形 (数论 + 黄金分割点)+ JOISC 2016 Day3 T3 「电报」(基环树 + 拓扑排序)

文章目录T1&#xff1a;等比数列三角形题目题解代码实现T2&#xff1a;电报题目题解代码实现T1&#xff1a;等比数列三角形 题目 求三边都是 ≤n 的整数&#xff0c;且成等比数列的三角形个数 注意三角形面积不能为 0 注意 oeis 中未收录此数列&#xff0c;所以并不需要去搜了…