FHQ Treap
FHQ Treap (%%%发明者范浩强年年NOI金牌)是一种神奇的数据结构,也叫非旋Treap,它不像Treap zig zag搞不清楚(所以叫非旋嘛),也不像Splay完全看不懂,而且它能完成Treap与Splay能完成的所有事,代码短,理解也容易。
基本操作
FHQ Treap和Treap很像,都是给每个节点一个随机的权值,使它满足堆的性质。建议先了解Treap(没必要实现,懂得原理即可)。不过,如果有两个节点值相同,FHQ Treap不会用一个数组cnt记录个数,而是直接再开一个节点。
FHQ的基本操作只有两个:Split与Merge。
Split表示把一棵树分成两棵,Merge表示把一棵树合并成一棵。
变量&函数约定
int L[MAXN], R[MAXN], sz[MAXN], rk[MAXN], val[MAXN], tot; int root; int New( int v ){ return val[++tot] = v, rk[tot] = rand(), L[tot] = R[tot] = 0, sz[tot] = 1, tot; } #define Updata(x) sz[x] = sz[L[x]] + sz[R[x]] + 1
没写成结构体,没写成指针。
表示
的左儿子,
表示
的右儿子,
表示以
为根的子树包含的节点数,
表示为了保持平衡随机赋予的权值,
表示该节点保存的值,
表示节点数,
表示当前的根节点。
表示新建一个值为
的节点(可以看成一棵只有一个节点平衡树)
表示更新节点
的
提醒:这里“值”与“权值”是不一样的,“值”表示节点保存的值,“权值“仅仅用于维持平衡,注意区分
Split
怎么分割呢?
常见的分割方法有两种,一种是按值分,一种是按排名分(实现差不多,这里只讲按值分)。
先来看看定义。
void Split( int c, int k, int &x, int &y );
c表示当前要分割的树的根节点,并且把值的节点分割出来,构成一棵树,把
赋为根节点,其他节点另外构成一棵树,把
赋为其根节点。
、
用引用(&)更方便处理。
对于当前的树,如果根节点的值
,
的左子树也全部
,所以我们可以把
赋为
,保留左子树,将右子树
的部分分割出来作为
的右子树。剩下的部分自然也就是在
的部分。
的情况同理。具体我们用递归实现。
void Split( int c, int k, int &x, int &y ){ if ( c == 0 ){ x = y = 0; return; }//如果当前处理的树为空,分出的两个子树当然也为空,所以直接赋值返回。 if ( val[c] <= k ) x = c, Split( R[c], k, R[x], y );//如果根节点值小于等于k,把x赋为c,继续处理右子树,并把小于等于k的部分分到x的右子树,其他分到y else y = c, Split( L[c], k, x, L[y] ); Updata(c);//别忘了更新sz }
Merge
上面分割的操作不会改变堆的性质与二叉查找树的性质,但是在合并的时候要注意保持堆的性质。
void Merge( int &c, int x, int y );
表示把以和
为根节点的树合并,将
赋为根节点。
注意:上面分割时x的所有节点的值都小于y的,合并时也要注意x的所有节点小于等于y,否则会出错
由于与
的权值在两颗树中是最大的,所以合并后的树根节点不是
就是
。所以比较
与
的权值就可以判断谁为根节点。
假设以为根。因为保证
的所有节点的值都小于等于
的,所以
肯定会合并在
的右子树。所以,我们不用动
的左子树,合并
的右子树与
作为
的右子树。
为根时同理。这样,就巧妙完成了同时维护堆的性质与二叉查找树的性质。
我们还是用递归。
void Merge( int &c, int x, int y ){ if ( !x || !y ){ c = x | y; return; } if ( rk[x] >= rk[y] ) c = x, Merge( R[x], R[c], y ); else c = y, Merge( L[y], x, L[c] ); Updata(c); }
我刚开始也理解不了这两种操作。主要瓶颈在难以想象。其实可以看做只处理当前的,未处理的留到下一步,反正操作方法都一样。
剩下的都可以用这两种操作实现。
插入操作
直接把它分成的树和
的树,将新建的节点与
的树合并,再与
树合并即可。
//opt 1 void Ins( int v ){ int x, y, z(New(v)); Split( root, v, x, y ); Merge( x, x, z ); Merge( root, x, y ); }
删除操作
分成和
两颗树,再分成
、
、
三棵树,将
左右子树合并,相当于删去
的一个节点,然后将三棵树重新合并即可。
// opt 2 void Del( int v ){ int x, y, z; Split( root, v, x, y ); Split( x, v - 1, x, z ); Merge( z, L[z], R[z] ); Merge( x, x, z ); Merge( root, x, y ); }
查询排名
其实可以用while
循环,,,但是,,,我,,,懒,,,所,,,以,,,直,,,接,,,,,,,
//opt 3 int GetRankByVal( int v ){ int x, y, t; Split( root, v - 1, x, y ); t = sz[x]; Merge( root, x, y ); return t + 1; }
查询值
这真的不能用Split和Merge偷懒了,,,所以乖乖写个while
吧~
技术含量不高,自行理解。
//opt 4 int GetValByRank( int rk ){ int c(root); while( c ){ if ( sz[L[c]] + 1 == rk ) return val[c]; else if ( sz[L[c]] >= rk ) c = L[c]; else rk -= 1 + sz[L[c]], c = R[c]; } return -1;//题目没要求。。。只是为了自己查错 }
查询前缀
分成两颗树与
,在
树中找最大值即可。
//opt 5 int GetPre( int v ){ int x, y, z; Split( root, v - 1, x, y ); z = x; while( R[z] ) z = R[z]; Merge( root, x, y ); return val[z]; }
查询后缀
与查询前缀同理。
//opt 6 int GetNxt( int v ){ int x, y, z; Split( root, v, x, y ); z = y; while( L[z] ) z = L[z]; Merge( root, x, y ); return val[z]; }
完整代码
#include<bits/stdc++.h> using namespace std; #define MAXN 100005 int L[MAXN], R[MAXN], sz[MAXN], rk[MAXN], val[MAXN], tot; int root; int New( int v ){ return val[++tot] = v, rk[tot] = rand(), L[tot] = R[tot] = 0, sz[tot] = 1, tot; } #define Updata(x) sz[x] = sz[L[x]] + sz[R[x]] + 1 void Split( int c, int k, int &x, int &y ){ if ( c == 0 ){ x = y = 0; return; } if ( val[c] <= k ) x = c, Split( R[c], k, R[x], y ); else y = c, Split( L[c], k, x, L[y] ); Updata(c); } void Merge( int &c, int x, int y ){ if ( !x || !y ){ c = x | y; return; } if ( rk[x] >= rk[y] ) c = x, Merge( R[x], R[c], y ); else c = y, Merge( L[y], x, L[c] ); Updata(c); } //opt 1 void Ins( int v ){ int x, y, z(New(v)); Split( root, v, x, y ); Merge( x, x, z ); Merge( root, x, y ); } // opt 2 void Del( int v ){ int x, y, z; Split( root, v, x, y ); Split( x, v - 1, x, z ); Merge( z, L[z], R[z] ); Merge( x, x, z ); Merge( root, x, y ); } //opt 3 int GetRankByVal( int v ){ int x, y, t; Split( root, v - 1, x, y ); t = sz[x]; Merge( root, x, y ); return t + 1; } //opt 4 int GetValByRank( int rk ){ int c(root); while( c ){ if ( sz[L[c]] + 1 == rk ) return val[c]; else if ( sz[L[c]] >= rk ) c = L[c]; else rk -= 1 + sz[L[c]], c = R[c]; } return -1; } //opt 5 int GetPre( int v ){ int x, y, z; Split( root, v - 1, x, y ); z = x; while( R[z] ) z = R[z]; Merge( root, x, y ); return val[z]; } //opt 6 int GetNxt( int v ){ int x, y, z; Split( root, v, x, y ); z = y; while( L[z] ) z = L[z]; Merge( root, x, y ); return val[z]; } int T; int main(){ srand(time(0));//随机数种子别忘了 root = New(INT_MAX);//虚节点,避免一个节点都没有不方便合并。注意要用一个很大的数,查询排名时就不用-1 scanf( "%d", &T ); while( T-- ){ int opt, x; scanf( "%d%d", &opt, &x ); switch( opt ){ case 1: Ins(x); break; case 2: Del(x); break; case 3: printf( "%d\n", GetRankByVal(x) ); break; case 4: printf( "%d\n", GetValByRank(x) ); break; case 5: printf( "%d\n", GetPre(x) ); break; case 6: printf( "%d\n", GetNxt(x) ); break; } } return 0; }
FHQ Treap还可以资瓷可持久化~比Treap、Splay好用多啦