您的位置:首页 > 其它

伸展树应用初步——解决区间问题

2014-05-09 21:44 357 查看
伸展树的基本操作就是伸展,也就是将指定节点旋转至树根(同时不改变排序二叉树的性质)。在这个操作的基础上,配合节点中保存额外的数据域,伸展树可以完成多种任务,包括各种区间问题。

伸展树的节点除了保存必要的指针信息和键值对之外,经常使用的额外的数据域包括size域、sum域、极值域等等。size域用于记录该节点所代表的子树的节点总数,可以用于解决区间kth数问题;sum域用于记录该节点所代表子树的所有节点的数值之和,可以解决区间和问题;极值域用于记录该子树所有节点的极值,可以解决RMQ问题。而且伸展树能够解决问题的范围非常广。不但可以解决静态区间问题,数值单点修改的区间问题,数值成段修改的区间问题(这需要延迟标记,本文暂不讨论),而且可以解决区间本身发生变动时问题。相比之下,ST算法只能解决静态RMQ,树状数组可以解决单点修改的区间和问题,线段树还能解决成段修改的区间问题,但是它们都不如伸展树覆盖的范围大,都不能解决最后一类问题。当然,伸展树适用范围虽然广,但是在解决特定问题时效率比较低,除非是特定的访问模式。

此外,伸展树还是其他高级算法和结构的基础,例如树链剖分和link-cut tree。所以其一伸展树是非常有用的结构,其二伸展树一招鲜是不行的,树状数组、线段树也都少不了。

伸展树中加入额外域的核心问题就是维护问题,也就是如何在伸展过程中维护额外域的正确性。首先需要加入一个操作称之为pushUp(t)函数。该函数的涵义是利用t及其子节点计算t的额外域(无论是size、sum还是极值,这一点都非常容易完成)。而本文以极值域解决单点修改RMQ问题为例进行说明,至于其他类型的域只有少许细节不同。

令旋转的节点为t,其父节点为P,无论左旋还是右旋,只有这两个节点的极值域会发生变化,其他相关节点均不变。所以在旋转操作中需要加入维护P和t的操作。

但是旋转操作从来只会在伸展操作中调用,绝不会被其他东西调用。而伸展操作将会重复旋转t,这也就意味着t将被维护多次,而实际上是不需要的。因为在伸展过程中,t的额外信息如果不对,对上对下均不会造成不利影响。所以只需在伸展结束之后把t维护一次即可,而旋转操作中只维护P即可。

查询操作完成很简单,假设要区间[s, e]的极值,则将节点s-1伸展成树根,再将节点e+1伸展为根节点的右儿子,那么节点e+1的左子树就代表了区间[s, e],节点e+1的左儿子的极值域就是结果。此处必须保证节点s-1和e+1存在,这是一个很简单的技巧,很容易实现。

单点修改操作也很容易完成,假设要修改的键值为key,则将key所在的节点旋转至树根,修改数值域,再更新这一个节点的极值域即可。

另外,初始时n个节点的伸展树的建树操作可以直接递归实现,而不必一个节点一个节点的插入,后者显然效率极低。

下列源代码是hdu1754的AC代码,典型的单点修改RMQ应使用线段树解决。此处故意使用伸展树解决(毫无疑问的,如无意外,伸展树的运行时间比线段树长)。

//RMQ with Splay Tree

#include <cstdio>
#include <cstring>

#define SIZE 200003

#define LEFT  0
#define RIGHT 1

typedef int key_t;
typedef int data_t;

struct _t{
int parent;
int child[2];
int sn;
key_t key;
data_t data;
data_t peak;
}Node[SIZE];
int toUsed = 0;
int Root = 0;

//初始化
inline void init(){
toUsed = Root = 0;
}

//信息上传,指根据儿子节点计算父节点的附加值,此处即区间最大值
inline void _pushUp(int t){
Node[t].peak = Node[t].data;
int son = Node[t].child[LEFT];
if ( son && Node[t].peak < Node[son].peak ) Node[t].peak = Node[son].peak;
son = Node[t].child[RIGHT];
if ( son && Node[t].peak < Node[son].peak ) Node[t].peak = Node[son].peak;
}

//分配一个新节点
inline int _newNode(){
++toUsed;
memset(Node+toUsed,0,sizeof(_t));
return toUsed;
}

//link操作
inline void _link(int p,int sn,int t){
Node[p].child[sn] = t;
if ( t ) Node[t].parent = p, Node[t].sn = sn;
}

//旋转操作,t非根节点
inline void _rotate(int t){
int p = Node[t].parent;
int sn = Node[t].sn;
int osn = sn ^ 1;

//确定三对父子关系
_link(p,sn,Node[t].child[osn]);
_link(Node[p].parent,Node[p].sn,t);
_link(t,osn,p);

//只维持p,t在伸展的最后维持
_pushUp(p);
}

//将t伸展至p下,p为0则伸展至树根
void _splay(int t,int p,int& root){
while( Node[t].parent != p ){
int pp = Node[t].parent;
if ( Node[pp].parent != p ) Node[pp].sn == Node[t].sn ? _rotate(pp) : _rotate(t);
_rotate(t);
}
_pushUp(t);
if ( 0 == p ) root = t;
}

//在root树上查找键值为key的节点,其父亲放在参数3中返回
int _advance(int root,key_t key,int&parent){
if ( 0 == root ) return parent = 0;
parent = 0;
int t = root;
while( t && Node[t].key != key ){
parent = t;
t = key < Node[t].key ? Node[t].child[LEFT] : Node[t].child[RIGHT];
}
return t;
}

//递归建树,因为key值是连续的
void build(int&t,int parent,int sn,key_t s,key_t e,data_t const a[]){
key_t mid = ( s + e ) >> 1;
t = _newNode();
Node[t].parent = parent;
Node[t].data = a[mid];
Node[t].key = mid;
Node[t].sn = sn;
if ( mid > s ) build(Node[t].child[LEFT],t,LEFT,s,mid-1,a);
if ( mid < e ) build(Node[t].child[RIGHT],t,RIGHT,mid+1,e,a);
_pushUp(t);
}

//查询操作,保证s-1和e+1存在
int query(int& root,key_t s,key_t e){
//将s-1伸展至树根
int p;
int t = _advance(root,s-1,p);
_splay(t,0,root);

//将e+1变成树根的右儿子
t = _advance(root,e+1,p);
_splay(t,root,root);

return Node[Node[t].child[LEFT]].peak;
}

//将键值为key的节点的数据域修改为v
void modify(int &root,key_t key,data_t v){
//将key节点伸展至树根
int p;
int t = _advance(root,key,p);
_splay(t,0,root);
//修改
Node[t].data = v;
if ( Node[t].peak < v ) Node[t].peak = v;
}

int N,M;
int A[SIZE] = {0};
bool read(){
if ( EOF == scanf("%d%d",&N,&M) ) return false;
for(int i=1;i<=N;++i)scanf("%d",A+i);
A[N+1] = 0;
return true;
}

int main(){
while ( read() ){
init();
build(Root,0,0,0,N+1,A);

while(M--){
char cmd[3];
int a,b;
scanf("%s%d%d",cmd,&a,&b);
if ( 'Q' == *cmd ){
printf("%d\n",query(Root,a,b));
}else{
modify(Root,a,b);
}
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: