概念
Treap一词是Tree和Heap的合成词,也就是这是一颗带有堆性质的树,即树堆,Treap树是一种排序二叉树(二叉搜索树、二分检索树 Binary Serach Tree),简称BST,也就是满足value值大小关系是左孩子<根<右孩子,这样就满足了排序二叉树,刚才讲过,Treap树还满足堆的性质,那么它的哪个值满足堆的性质呢,并不是value,而是一个优先级rank值,这个rank值是人为添加的,一般使用一个随机数,这个rank是为了维持二叉树的平衡而设定的,总的说来,对于键值来说,Treap是一棵排序二叉树,对于优先级来说,Treap是一个堆,当然根节点的优先级是最高的
实现
结构
首先考虑树的组成,需要键值value,优先级Rank,为了方便统计排名,需要一个sz记录当前树的节点数,需要一个cnt记录当前数字出现了多少次,需要两个指针分别指向左孩子和右孩子,每次操作之后都需要更新当前树上节点数,所以写一个Update函数,为了方便判断当前元素和根节点value的大小关系,需要写一个cmp函数,如果小于则在左子树,大于在右子树,相等返回
−
1
-1
− 1
struct TreapNode{
int value;
int Rank;
int cnt;
int sz;
TreapNode* son[ 2 ] ;
int cmp ( int x) const {
if ( x == value) return - 1 ;
return x < value ? 0 : 1 ;
}
void Update ( ) {
sz = 1 ;
if ( son[ 0 ] != NULL ) sz + = son[ 0 ] - > sz;
if ( son[ 1 ] != NULL ) sz + = son[ 1 ] - > sz;
}
} ;
旋转操作
为什么要旋转?考虑普通的排序二叉树,假设插入数字是有序的,那么二叉树就会退化为一条链,这样查询就和数组没有什么区别了,所以Treap树就制定了一个随机给的rank值,按照它的大小关系进行相应的旋转,使得二叉树保持平衡不退化为链
左旋和右旋
上图针对于9进行了旋转,从左到右是右旋,从右到左是左旋,这里可以理解为右旋为顺时针,左旋为逆时针
右旋
这个图,通俗一点讲,右旋要断三根线:待旋节点和它的父亲节点、待旋节点和左孩子、左孩子和它的右孩子,然后把左孩子的右孩子作为待旋节点的左孩子,待旋节点作为它左孩子的右孩子,再把待旋节点左孩子放在待旋节点的位置上,这里注意一点,我们要传的是待旋节点的引用,引用不改变地址,只改变值 ,这样我们才能让6到9的位置上而依然保持和9父亲之间的关系
左旋
相对的,左旋是先向右,再向左,也就是找右孩子,再找右孩子的左孩子,让右孩子的左孩子作为待旋节点的右孩子,待旋节点作为它右孩子的左孩子,完成左旋操作
旋转总结
可以发现,左旋和右旋是一种互逆的关系,右旋找左孩子,左旋找右孩子,这样可以通过
1
−
x
1 - x
1 − x 的操作来实现,更快的方法是取异或,程序如下
void Rotate ( TreapNode* & o, int d) {
TreapNode* p = o- > son[ d ^ 1 ] ;
o- > son[ d ^ 1 ] = p- > son[ d] ;
p- > son[ d] = o;
o- > Update ( ) ;
p- > Update ( ) ;
o = p;
}
插入操作
解决了旋转,插入就简单的多了,每次插入一个数,首先根据value确定位置(排序二叉树),确定位置以后,随机给定Rank值,如果根节点Rank不是最大,则需要旋转
void Insert ( TreapNode* & o, int k) {
if ( o == NULL ) {
o = new TreapNode ( ) ;
o- > value = k;
o- > Rank = rand ( ) ;
o- > son[ 0 ] = o- > son[ 1 ] = NULL ;
o- > sz = 1 ;
o- > cnt = 1 ;
} else {
int d = o- > cmp ( k) ;
if ( d != - 1 ) {
Insert ( o- > son[ d] , k) ;
if ( o- > Rank < o- > son[ d] - > Rank) Rotate ( o, d ^ 1 ) ;
} else o- > cnt++ ;
}
o- > Update ( ) ;
}
删除操作
先看这个值在哪棵子树,然后要看这个值是否有多个,如果有多个,
c
n
t
−
−
cnt--
c n t − − 即可,注意update;如果只有一个,那么需要删除该节点,但是不能直接删除,需要判断左右孩子的优先级保证树的平衡,如果左孩子优先级高,那么节点右旋,此时左孩子转到根,原来的根转到右子树,所以需要递归在右子树中删除根节点 如果该节点不是左孩子右孩子都存在,那么直接把存在的那个孩子移动到根,然后直接删除原来的根节点即可,最后如果根节点不是空指针,那么需要update
void Remove ( TreapNode* & o, int k) {
int d = o- > cmp ( k) ;
if ( d == - 1 ) {
if ( o- > cnt > 1 ) {
o- > cnt-- ;
o- > Update ( ) ;
return ;
}
TreapNode* u = o;
if ( o- > son[ 0 ] != NULL && o- > son[ 1 ] != NULL ) {
int d2 = o- > son[ 0 ] - > Rank > o- > son[ 1 ] - > Rank ? 1 : 0 ;
Rotate ( o, d2) ;
Remove ( o- > son[ d2] , k) ;
} else {
if ( o- > son[ 0 ] == NULL ) o = o- > son[ 1 ] ;
else o = o- > son[ 0 ] ;
delete u;
}
} else {
Remove ( o- > son[ d] , k) ;
}
if ( o != NULL ) o- > Update ( ) ;
}
第K大
在这里,我们设定的sz派上了场,从根节点出发,如果K小于右子树的节点数,那么就在右子树里面找第K大(排序二叉树);如果K在右子树节点值的个数范围内,那么就是根节点对应的value;否则就在左子树里面找第(K
−
-
− 右子树的sz
−
-
− 出现次数)大,当然需要再处理一些特殊的情况比如越界
int Get_Kth ( TreapNode* o, int k) {
if ( o == NULL || k <= 0 || k > o- > sz) return 0 ;
int s = ( o- > son[ 1 ] == NULL ? 0 : o- > son[ 1 ] - > sz) ;
if ( k >= s + 1 && k <= s + o- > cnt) return o- > value;
else if ( k <= s) return Get_Kth ( o- > son[ 1 ] , k) ;
else return Get_Kth ( o- > son[ 0 ] , k - s - o- > cnt) ;
}
K是第几大
如果K不在树上,那么返回
−
1
-1
− 1 (有些题目有特殊要求),否则递归查找即可
int Get_Xrank ( TreapNode* o, int k) {
if ( o == NULL ) return - 1 ;
int d = o- > cmp ( k) ;
if ( d == - 1 ) return o- > son[ 1 ] == NULL ? 1 : o- > son[ 1 ] - > sz + 1 ;
else if ( d == 1 ) return Get_Xrank ( o- > son[ 1 ] , k) ;
else {
int tmp = Get_Xrank ( o- > son[ 0 ] , k) ;
if ( tmp == - 1 ) return - 1 ;
return tmp + ( o- > son[ 1 ] == NULL ? o- > cnt : o- > son[ 1 ] - > sz + o- > cnt) ;
}
}
找前驱和后继
一般对于K的前驱的定义是小于K的最大值,那么对于排序二叉树,从根节点出发,如果根节点value小于K,那么更新前驱,转向右孩子,递归查找可能的更大的前驱;如果根节点value大于或等于K,说明value大了,那么递归查找左子树直到找到答案,找不到就返回一个特殊标记
int Get_Pre ( TreapNode* o, int k) {
int pre = - INF;
while ( o != NULL ) {
if ( o- > value >= k) {
o = o- > son[ 0 ] ;
} else {
pre = o- > value;
o = o- > son[ 1 ] ;
}
}
return pre;
}
int Get_Next ( TreapNode* o, int k) {
int Next = INF;
while ( o != NULL ) {
if ( o- > value <= k) {
o = o- > son[ 1 ] ;
} else {
Next = o- > value;
o = o- > son[ 0 ] ;
}
}
return Next;
}
练习
普通平衡树
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f ;
struct TreapNode{
int value;
int Rank;
int sz;
int cnt;
TreapNode* son[ 2 ] ;
int cmp ( int k) const {
if ( k == value) return - 1 ;
return k < value ? 0 : 1 ;
}
void Update ( ) {
sz = cnt;
if ( son[ 0 ] != NULL ) sz + = son[ 0 ] - > sz;
if ( son[ 1 ] != NULL ) sz + = son[ 1 ] - > sz;
}
} ;
void Rotate ( TreapNode* & o, int d) {
TreapNode* p = o- > son[ d ^ 1 ] ;
o- > son[ d ^ 1 ] = p- > son[ d] ;
p- > son[ d] = o;
o- > Update ( ) ;
p- > Update ( ) ;
o = p;
}
void Insert ( TreapNode* & o, int k) {
if ( o == NULL ) {
o = new TreapNode ( ) ;
o- > son[ 0 ] = o- > son[ 1 ] = NULL ;
o- > value = k;
o- > Rank = rand ( ) ;
o- > sz = 1 ;
o- > cnt = 1 ;
} else {
int d = o- > cmp ( k) ;
if ( d == - 1 ) {
o- > cnt++ ;
} else {
Insert ( o- > son[ d] , k) ;
if ( o- > Rank < o- > son[ d] - > Rank) Rotate ( o, d ^ 1 ) ;
}
}
o- > Update ( ) ;
}
void Remove ( TreapNode* & o, int k) {
int d = o- > cmp ( k) ;
if ( d == - 1 ) {
if ( o- > cnt > 1 ) {
o- > cnt-- ;
o- > Update ( ) ;
return ;
} else {
TreapNode* u = o;
if ( o- > son[ 0 ] != NULL && o- > son[ 1 ] != NULL ) {
int d2 = o- > son[ 0 ] - > Rank > o- > son[ 1 ] - > Rank ? 1 : 0 ;
Rotate ( o, d2) ;
Remove ( o- > son[ d2] , k) ;
} else {
if ( o- > son[ 0 ] == NULL ) o = o- > son[ 1 ] ;
else o = o- > son[ 0 ] ;
delete u;
}
}
} else Remove ( o- > son[ d] , k) ;
if ( o != NULL ) o- > Update ( ) ;
}
int Get_Pre ( TreapNode* o, int k) {
int pre = - INF;
while ( o != NULL ) {
if ( o- > value < k) {
pre = o- > value;
o = o- > son[ 1 ] ;
} else o = o- > son[ 0 ] ;
}
return pre;
}
int Get_Next ( TreapNode* o, int k) {
int Next = INF;
while ( o != NULL ) {
if ( o- > value > k) {
Next = o- > value;
o = o- > son[ 0 ] ;
} else o = o- > son[ 1 ] ;
}
return Next;
}
int Get_Kth ( TreapNode* o, int k) {
if ( o == NULL || o- > sz < k || k <= 0 ) return - 1 ;
int s = o- > son[ 0 ] == NULL ? 0 : o- > son[ 0 ] - > sz;
if ( k >= s + 1 && k <= s + o- > cnt) return o- > value;
else if ( k <= s) return Get_Kth ( o- > son[ 0 ] , k) ;
else return Get_Kth ( o- > son[ 1 ] , k - o- > cnt - s) ;
}
int Get_Krank ( TreapNode* o, int k) {
if ( o == NULL ) return - 1 ;
int d = o- > cmp ( k) ;
if ( d == - 1 ) return o- > son[ 0 ] == NULL ? 1 : o- > son[ 0 ] - > sz + 1 ;
else if ( d == 0 ) return Get_Krank ( o- > son[ 0 ] , k) ;
else {
int tmp = Get_Krank ( o- > son[ 1 ] , k) ;
if ( tmp == - 1 ) return - 1 ;
else return tmp + ( o- > son[ 0 ] == NULL ? o- > cnt : o- > son[ 0 ] - > sz + o- > cnt) ;
}
}
int main ( ) {
int n, op, x;
TreapNode* root = NULL ;
scanf ( "%d" , & n) ;
while ( n-- ) {
scanf ( "%d%d" , & op, & x) ;
if ( op == 1 ) Insert ( root, x) ;
else if ( op == 2 ) Remove ( root, x) ;
else if ( op == 3 ) printf ( "%d\n" , Get_Krank ( root, x) ) ;
else if ( op == 4 ) printf ( "%d\n" , Get_Kth ( root, x) ) ;
else if ( op == 5 ) printf ( "%d\n" , Get_Pre ( root, x) ) ;
else printf ( "%d\n" , Get_Next ( root, x) ) ;
}
return 0 ;
}
加强的模板题
主要是卡线段树,使用Treap无影响 相对于上一道题需要改一个函数,因为x可能不存在,所以在查询 x是第几大的时候如果没查到不能返回-1,而是返回1(考虑root为空指针)
int Get_Xrank ( TreapNode* o, int k) {
if ( o == NULL ) return 1 ;
int d = o- > cmp ( k) ;
if ( d == - 1 ) return o- > son[ 0 ] == NULL ? 1 : o- > son[ 0 ] - > sz + 1 ;
else if ( d == 0 ) return Get_Xrank ( o- > son[ 0 ] , k) ;
else {
int tmp = Get_Xrank ( o- > son[ 1 ] , k) ;
return tmp + ( o- > son[ 0 ] == NULL ? o- > cnt : o- > son[ 0 ] - > sz + o- > cnt) ;
}
}
poj3481
同样的模板题,这里面的priority可以作为value,最后输出Rank,反着输入直接套模板
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f ;
struct TreapNode{
int value;
int Rank;
int sz;
int cnt;
TreapNode* son[ 2 ] ;
int cmp ( int k) const {
if ( k == value) return - 1 ;
return k < value ? 0 : 1 ;
}
void Update ( ) {
sz = cnt;
if ( son[ 0 ] != NULL ) sz + = son[ 0 ] - > sz;
if ( son[ 1 ] != NULL ) sz + = son[ 1 ] - > sz;
}
} ;
void Rotate ( TreapNode* & o, int d) {
TreapNode* p = o- > son[ d ^ 1 ] ;
o- > son[ d ^ 1 ] = p- > son[ d] ;
p- > son[ d] = o;
o- > Update ( ) ;
p- > Update ( ) ;
o = p;
}
void Insert ( TreapNode* & o, int k, int priority) {
if ( o == NULL ) {
o = new TreapNode ( ) ;
o- > son[ 0 ] = o- > son[ 1 ] = NULL ;
o- > value = k;
o- > Rank = priority;
o- > sz = 1 ;
o- > cnt = 1 ;
} else {
int d = o- > cmp ( k) ;
if ( d == - 1 ) {
o- > cnt++ ;
} else {
Insert ( o- > son[ d] , k, priority) ;
if ( o- > Rank < o- > son[ d] - > Rank) Rotate ( o, d ^ 1 ) ;
}
}
o- > Update ( ) ;
}
int FIND_MAX ( TreapNode* & o) {
if ( o == NULL ) return 0 ;
if ( o- > son[ 1 ] == NULL ) {
int ans = o- > Rank;
TreapNode* u = o;
o = o- > son[ 0 ] ;
delete u;
u = NULL ;
return ans;
}
return FIND_MAX ( o- > son[ 1 ] ) ;
}
int FIND_MIN ( TreapNode* & o) {
if ( o == NULL ) return 0 ;
if ( o- > son[ 0 ] == NULL ) {
int ans = o- > Rank;
TreapNode* u = o;
o = o- > son[ 1 ] ;
delete u;
u = NULL ;
return ans;
}
return FIND_MIN ( o- > son[ 0 ] ) ;
}
int main ( ) {
int n, k, p;
TreapNode* root = NULL ;
while ( ~ scanf ( "%d" , & n) && n) {
if ( n == 1 ) {
scanf ( "%d%d" , & k, & p) ;
Insert ( root, p, k) ;
} else if ( n == 2 ) {
printf ( "%d\n" , FIND_MAX ( root) ) ;
} else printf ( "%d\n" , FIND_MIN ( root) ) ;
}
return 0 ;
}
营业额统计 洛谷 营业额统计 BZOJ
两个oj上面这道题题面相同,这个题显然是要求Treap树上离自己value最近的差的绝对值加和即为答案,这里需要改一下前驱和后继,因为这里面可以相等
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f ;
struct TreapNode{
int value;
int Rank;
int sz;
int cnt;
TreapNode* son[ 2 ] ;
int cmp ( int k) const {
if ( k == value) return - 1 ;
return k < value ? 0 : 1 ;
}
void Update ( ) {
sz = cnt;
if ( son[ 0 ] != NULL ) sz + = son[ 0 ] - > sz;
if ( son[ 1 ] != NULL ) sz + = son[ 1 ] - > sz;
}
} ;
void Rotate ( TreapNode* & o, int d) {
TreapNode* p = o- > son[ d ^ 1 ] ;
o- > son[ d ^ 1 ] = p- > son[ d] ;
p- > son[ d] = o;
o- > Update ( ) ;
p- > Update ( ) ;
o = p;
}
void Insert ( TreapNode* & o, int k) {
if ( o == NULL ) {
o = new TreapNode ( ) ;
o- > son[ 0 ] = o- > son[ 1 ] = NULL ;
o- > value = k;
o- > Rank = rand ( ) ;
o- > sz = 1 ;
o- > cnt = 1 ;
} else {
int d = o- > cmp ( k) ;
if ( d == - 1 ) {
o- > cnt++ ;
} else {
Insert ( o- > son[ d] , k) ;
if ( o- > Rank < o- > son[ d] - > Rank) Rotate ( o, d ^ 1 ) ;
}
}
o- > Update ( ) ;
}
int Get_Pre ( TreapNode* o, int k) {
int pre = - INF;
while ( o != NULL ) {
if ( o- > value > k) {
o = o- > son[ 0 ] ;
} else {
pre = o- > value;
o = o- > son[ 1 ] ;
}
}
return pre;
}
int Get_Next ( TreapNode* o, int k) {
int Next = INF;
while ( o != NULL ) {
if ( o- > value < k) {
o = o- > son[ 1 ] ;
} else {
Next = o- > value;
o = o- > son[ 0 ] ;
}
}
return Next;
}
int main ( ) {
int n, k, p;
TreapNode* root = NULL ;
scanf ( "%d" , & n) ;
int ans = 0 ;
while ( n-- ) {
scanf ( "%d" , & k) ;
if ( root == NULL ) ans + = k;
else {
ans + = min ( k - Get_Pre ( root, k) , Get_Next ( root, k) - k) ;
}
Insert ( root, k) ;
}
printf ( "%d" , ans) ;
return 0 ;
}
洛谷上面没问题,但是BZOJ就是0分,看了kuangbin博客发现输入里面需要加这样一句话
if ( scanf ( "%d" , & k) == EOF ) k = 0 ;
搞不懂~~~ 郁闷的出纳员 洛谷 这道题需要使用一个delta来保存当前工资变化情况,有一个技巧是插入的时候带着delta一起插入,因为delt存储的是所有人的工资变化,这样便于统计 当扣钱的时候需要判断有没有员工退出,如果有,把他们删除掉
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f ;
struct TreapNode{
int value;
int Rank;
int sz;
int cnt;
TreapNode* son[ 2 ] ;
int cmp ( int k) const {
if ( k == value) return - 1 ;
return k < value ? 0 : 1 ;
}
void Update ( ) {
sz = cnt;
if ( son[ 0 ] != NULL ) sz + = son[ 0 ] - > sz;
if ( son[ 1 ] != NULL ) sz + = son[ 1 ] - > sz;
}
} ;
void Rotate ( TreapNode* & o, int d) {
TreapNode* p = o- > son[ d ^ 1 ] ;
o- > son[ d ^ 1 ] = p- > son[ d] ;
p- > son[ d] = o;
o- > Update ( ) ;
p- > Update ( ) ;
o = p;
}
void Insert ( TreapNode* & o, int k) {
if ( o == NULL ) {
o = new TreapNode ( ) ;
o- > son[ 0 ] = o- > son[ 1 ] = NULL ;
o- > value = k;
o- > Rank = rand ( ) ;
o- > sz = 1 ;
o- > cnt = 1 ;
} else {
int d = o- > cmp ( k) ;
if ( d == - 1 ) {
o- > cnt++ ;
} else {
Insert ( o- > son[ d] , k) ;
if ( o- > Rank < o- > son[ d] - > Rank) Rotate ( o, d ^ 1 ) ;
}
}
o- > Update ( ) ;
}
int Get_Kth ( TreapNode* o, int k) {
if ( o == NULL || o- > sz < k || k <= 0 ) return - 1 ;
int s = o- > son[ 1 ] == NULL ? 0 : o- > son[ 1 ] - > sz;
if ( k >= s + 1 && k <= s + o- > cnt) return o- > value;
else if ( k <= s) return Get_Kth ( o- > son[ 1 ] , k) ;
else return Get_Kth ( o- > son[ 0 ] , k - o- > cnt - s) ;
}
int del ( TreapNode* & o, int k) {
if ( o == NULL ) return 0 ;
int t;
if ( o- > value < k) {
t = o- > son[ 0 ] == NULL ? o- > cnt : o- > son[ 0 ] - > sz + o- > cnt;
o = o- > son[ 1 ] ;
return t + del ( o, k) ;
} else {
t = del ( o- > son[ 0 ] , k) ;
o- > sz - = t;
return t;
}
}
int main ( ) {
int n, k, p, MIN;
int delta = 0 ;
int leave = 0 ;
char ch[ 3 ] ;
TreapNode* root = NULL ;
scanf ( "%d%d" , & n, & MIN) ;
while ( n-- ) {
scanf ( "%s%d" , ch, & k) ;
if ( ch[ 0 ] == 'I' ) {
if ( k >= MIN) Insert ( root, k - delta) ;
} else if ( ch[ 0 ] == 'A' ) {
delta + = k;
} else if ( ch[ 0 ] == 'S' ) {
delta - = k;
leave + = del ( root, MIN - delta) ;
} else {
if ( root == NULL || k > root- > sz) printf ( "-1\n" ) ;
else printf ( "%d\n" , Get_Kth ( root, k) + delta) ;
}
}
printf ( "%d" , leave) ;
return 0 ;
}