伸展树的节点的size域的应用

伸展树不是平衡二叉树,但是它的操作均摊是O( logN )的,只需要将增、删、查、改的节点都加以伸展即可。因此可以用来解决相关问题。

在伸展树的节点上附加一个size域,用来保存以该节点为根的子树的节点总数。这个size域可以用来解决很多问题。例如可以解决区间问题,例如SBT就是用这个size域来计算平衡条件。size域能够解决的最简单、最直接的问题就是kth问题——求第k小的数。

在伸展树上加上size域用来解决POJ2371

二叉树节点的结构体定义如下:

int const SIZE = 1111111;
int const LEFT = 0;
int const RIGHT = 1;
struct node_t{
    int parent;   //父节点
    int child[2]; //子节点
    int sn;       //指示本节点是其父亲左儿子还是右儿子
    key_t key;    //键
    value_t value;//值,这道题里实际上不需要
    int size;     //size域
}Node[SIZE];      //Node[0]不使用,用来充当NULL
int toUsed = 0;

这道题中用来充当键的就是一个整数,而且存在可能相等的情况。因此,将其出现的先后顺序考虑进去,从而确保任意两个节点具有不同的键。因此,键不是一个整数,而是一个结构体。同时为这个结构体重载相应的关系运算符。

struct key_t{
    int key;   //真正的键
    int order; //出现的顺序
    key_t(int a=0,int b=0):key(a),order(b){}
};
bool operator < (key_t const&l,key_t const&r){
    return l.key < r.key || ( l.key == r.key && l.order < r.order );
}
bool operator == (key_t const&l,key_t const&r){
    return l.key == r.key && l.order == r.order;
}
bool operator != (key_t const&l,key_t const&r){
    return !( l == r );
}

计算size域所用的函数

inline void _pushUp(int t){
    Node[t].size = 1;
    int son = Node[t].child[LEFT];
    if (son) Node[t].size += Node[son].size;
    son = Node[t].child[RIGHT];
    if (son) Node[t].size += Node[son].size;
}

伸展操作所用到的函数,在《伸展树的旋转和伸展操作》中有介绍。这个地方需要注意的是:要在合适的地方调用_pushUp(int)函数来维持size域

//设置p、t的父子关系,这里不调用_pushUp(int)
inline void _link(int p,int sn,int t){
    Node[p].child[sn] = t;
    Node[t].parent = p;
    Node[t].sn = sn;
}
//旋转就是重新确定三对父子关系,这里只维持节点p的size
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);

    _pushUp(p);
}
//伸展,只需在伸展的最后维持节点t的size
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;
    return;
}

再为伸展树提供3个辅助函数。

//获取一个新的可用节点
inline int _newNode(){
    ++toUsed;
    memset(Node+toUsed,0,sizeof(node_t));
    return toUsed;
}
//在root树上查找键为key的节点,parent为其父节点
int _advance(int root,int&parent,key_t key){
    if ( 0 == root ) return parent = 0;

    int t = root;
    parent = Node[t].parent;
    while( t && key != Node[t].key ){
        parent = t;
        t = key < Node[t].key ? Node[t].child[LEFT] : Node[t].child[RIGHT];
    }
    return t;
}
//在root树上递归查找第kth个数,编号从1开始。这是size应用的关键,但其实很好理解。
int _kth(int root,int kth){
    int son = Node[root].child[LEFT];
    int sz = son ? Node[son].size + 1 : 1;

    if ( kth < sz ) return _kth(son,kth);
    if ( sz < kth ) return _kth(Node[root].child[RIGHT],kth-sz);
    return root;
}

接下来为伸展数提供对外接口,根据这道题的要求,只需提供3个函数即可。

//初始化
inline void init(){
    toUsed = 0;
    memset(Node,0,sizeof(node_t));
}
//在root树上插入一个节点
void insert(int&root,key_t key,value_t value=0){
    int t = _newNode();
    Node[t].key = key;
    Node[t].value = value;
    Node[t].size = 1;

    if ( 0 == root ){
        root = t;
        return;
    }

    int p;
    _advance(root,p,key);

    int sn = key < Node[p].key ? LEFT : RIGHT;
    _link(p,sn,t);
    _splay(t,0,root);
}
//在root树上查找第kth数,编号从1开始
int select(int& root,int kth){
    int t = _kth(root,kth);
    _splay(t,0,root);
    return t;
}

接下来只要完成主函数即可。完整的代码如下。

#include <cstdio>
#include <cstring>
using namespace std;

int const SIZE = 1111111;
int const LEFT = 0;
int const RIGHT = 1;
struct key_t{
    int key;   
    int order;
    key_t(int a=0,int b=0):key(a),order(b){}
};
bool operator < (key_t const&l,key_t const&r){
    return l.key < r.key || (l.key == r.key && l.order < r.order);
}
bool operator == (key_t const&l,key_t const&r){
    return l.key == r.key && l.order == r.order;
}
bool operator != (key_t const&l,key_t const&r){
    return !(l == r);
}
typedef int value_t;
struct node_t{
    int parent;
    int child[2];
    int sn;
    key_t key;
    value_t value;
    int size;
}Node[SIZE];
int toUsed = 0;

inline void _pushUp(int t){
    Node[t].size = 1;
    int son = Node[t].child[LEFT];
    if (son) Node[t].size += Node[son].size;
    son = Node[t].child[RIGHT];
    if (son) Node[t].size += Node[son].size;
}
inline void _link(int p,int sn,int t){
    Node[p].child[sn] = t;
    Node[t].parent = p;
    Node[t].sn = sn;
}
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);

    _pushUp(p);
}
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;
    return;
}
inline void init(){
    toUsed = 0;
    memset(Node,0,sizeof(node_t));
}
inline int _newNode(){
    ++toUsed;
    memset(Node+toUsed,0,sizeof(node_t));
    return toUsed;
}
int _advance(int root,int&parent,key_t key){
    if ( 0 == root ) return parent = 0;

    int t = root;
    parent = Node[t].parent;
    while( t && key != Node[t].key ){
        parent = t;
        t = key < Node[t].key ? Node[t].child[LEFT] : Node[t].child[RIGHT];
    }
    return t;
}
void insert(int&root,key_t key,value_t value=0){
    int t = _newNode();
    Node[t].key = key;
    Node[t].value = value;
    Node[t].size = 1;

    if ( 0 == root ){
        root = t;
        return;
    }

    int p;
    _advance(root,p,key);

    int sn = key < Node[p].key ? LEFT : RIGHT;
    _link(p,sn,t);
    _splay(t,0,root);
}
int _kth(int root,int kth){
    int son = Node[root].child[LEFT];
    int sz = son ? Node[son].size + 1 : 1;

    if ( kth < sz ) return _kth(son,kth);
    if ( sz < kth ) return _kth(Node[root].child[RIGHT],kth-sz);
    return root;
}

int select(int& root,int kth){
    int t = _kth(root,kth);
    _splay(t,0,root);
    return t;
}

int main(){
    init();
    int root = 0;
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;++i){
        int x;
        scanf("%d",&x);
        insert(root,key_t(x,i));
    }
    char tmp[10];
    scanf("%s%d",tmp,&n);
    for(int i=0;i<n;++i){
        int k;
        scanf("%d",&k);
        int t = select(root,k);
        printf("%d\n",Node[t].key.key);
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值