splay的总结

前言

splay 是一种强大的数据结构,是可以动态维护树的状态的数据结构。那么这种结构的主要操作就是 splay splay 又是靠 rotate 实现的。所以 rotate 是最重要的。

那么先介绍 rotate ,这个 rotate 有许多种版本,根据我精心(为了少打码)研究,发现了一个很好的版本,这个版本,也使得其 splay 异常的美丽,这个版本是 Cloveunique 所写的。
传送门在此

我们经常看到标准的 splay 有左旋右旋之分,但是这个版本的则不用,因为 splay 的左右旋等的区分是看父亲,爷爷与它是不是一条链上,如果是,则先转父亲,反之就先转它,这样子,就可以很好的维护一下这个平衡树的性质,而不用莫名的一堆判断。(实际上指针版的 splay 更快,但是我太弱了,写不来啊。)

18.4.23

稍微补充一下指针版的splay以及其需要注意的部分。

#include <bits/stdc++.h>
#define ls ch[0]
#define rs ch[1]

using namespace std;
const int N = 1e5+10;
const int inf = 1e9;
struct Node {
    Node *ch[2],*fa;
    int cnt,sz,key;
    Node() {}
    Node(int key) :key(key) {
        ls = rs = fa = NULL;
        cnt = sz = 1;
    }
    void update() {
        sz = cnt + (ls?ls->sz:0) + (rs?rs->sz:0);
    }
}*root;


int get(Node *u){ return  u->fa->ch[1] == u; }
void rotate(Node *u){
    Node *old = u->fa , *oldfa = old->fa;
    if ( oldfa )
        oldfa -> ch[ oldfa->ch[1]==old ] = u;
    u -> fa = oldfa;
    int d = old -> ch[1] == u;
    old -> ch[d] = u -> ch[d^1];
    if (u -> ch[d^1] )u -> ch[d^1] -> fa = old;
    u -> ch[d^1] = old;
    old -> fa = u;
    old->update(); u-> update();
}

void splay(Node *u){
    for (Node *f ;  f = u -> fa  ; rotate(u) )
        if ( f -> fa )
            rotate( get(f)==get(u) ? f : u);
    root = u;
}


void insert(int key){
    if (!root) {
        root = new Node(key);
        return ;
    }
    int tmp;
    Node *cur = root , *last = NULL;
    while (1) {
        if ( !cur ) {
            cur = new Node(key);cur -> fa = last;
            last -> ch[ (last->key) < key ] = cur;last->sz++;
            splay(cur);
            return ;
        }
        if (cur->key == key) { cur -> cnt++; cur -> sz++; splay(cur); return; }
        last = cur;
        cur = cur->ch[cur -> key < key];
    }
}
void find(int key)
{
    Node *cur = root;
    while (cur) {
        if (cur->key == key ) {
            splay(cur);
            return;
        }
        cur = cur -> ch[ cur->key < key ];
    }
}
int key_find(int key){
    find(key);
    return root->ls ? root->ls->sz + 1 : 1;
}
int kth_find(int kth){
    Node *cur = root;
    while (true) {
        if ( cur->ls && cur->ls->sz >= kth ) {
            cur = cur->ls;
        } else {
            kth -= (cur->ls?cur->ls->sz:0) ;
            if (kth <= cur->cnt ) {
                splay(cur);
                return cur->key;
            }
            kth -= cur->cnt;
            cur = cur->rs;
        }
    }
}
Node* root_pre(){
    Node *cur = root->ls;
    while (cur->rs) cur =  cur->rs;
    return cur;
}

Node* root_suf(){
    Node *cur = root->rs;
    while (cur->ls) cur =  cur->ls;
    return cur;
}

void Delete(int key){
    find(key);
    if (root -> cnt > 1) {
        root -> cnt--;root -> sz--;
        return;
    }
    if (root -> ls == NULL && root -> rs == NULL ) { root = NULL; delete root; return; }
    if (root -> ls == NULL ) { Node* pre = root; root = root->rs; root -> fa = NULL; delete pre; return; }
    if (root -> rs == NULL ) { Node* pre = root; root = root->ls; root -> fa = NULL; delete pre; return; }
    Node* oldroot = root ; Node *lefbig = root_pre();splay(lefbig);
    root -> rs = oldroot -> rs;root -> rs -> fa = root;
    delete oldroot;
    root -> update();
}

int pre(int key){
    Node *cur = root;int ans;
    while (cur) {
        if (cur -> key < key ) ans = cur->key , cur = cur->rs;
        else cur = cur->ls;
    }
    return ans;
}
int suf(int key){
    Node *cur = root;
    int ans;
    while (cur) {
        if (cur -> key > key ) ans = cur->key , cur = cur->ls;
        else cur = cur->rs;
    }
    return ans;
}

那么稍微说一些比较重要的地方:
rotate

void rotate(Node *u){
    Node *old = u->fa , *oldfa = old->fa;
    if ( oldfa )
        oldfa -> ch[ oldfa->ch[1]==old ] = u;
    u -> fa = oldfa;
    int d = old -> ch[1] == u;
    old -> ch[d] = u -> ch[d^1];
    if (u -> ch[d^1] )u -> ch[d^1] -> fa = old;
    u -> ch[d^1] = old;
    old -> fa = u;
    old->update(); u-> update();
}

那么注意这个地方:

if (u -> ch[d^1] )u -> ch[d^1] -> fa = old;

因为有可能没有这个节点,所以要判一下。


int kth_find(int kth){
    Node *cur = root;
    while (true) {
        if ( cur->ls && cur->ls->sz >= kth ) {
            cur = cur->ls;
        } else {
            kth -= (cur->ls?cur->ls->sz:0) ;
            if (kth <= cur->cnt ) {
                splay(cur);
                return cur->key;
            }
            kth -= cur->cnt;
            cur = cur->rs;
        }
    }
}

这个是我发现的最好的 kthfind 函数。不解释。


int pre(int key){
    Node *cur = root;int ans;
    while (cur) {
        if (cur -> key < key ) ans = cur->key , cur = cur->rs;
        else cur = cur->ls;
    }
    return ans;
}

找前驱与后继也不用什么先插入再删除这种傻逼做法了。

void splay(Node *u,Node *goal)
{   
    for (Node *f ;   ( f = u -> fa ) != goal  ; rotate(u) ) 
    if ( (f -> fa) != goal  )
            rotate( get(f)==get(u) ? f : u);
    if ( goal == NULL ) root = u;
}

这里是把 u 旋转到goal的下面。
这里要注意f的判断。
还有注意,插入或者删除的时候可以不用 update ,直接 sz cnt 变化就可以了。

例题:

  • BZOJ 3224 Tyvj 1728:

模板题,不解释。

  • BZOJ 1208 [HNOI2004]:

用两棵 splay 模拟这个过程,然后根据加入的点来看如何加入,查查前驱,后继就可以了。但是要注意有可能没有前驱后继的情况,要特判一下。

重点: splay 维护区间信息。

splay 的操作可得,我们可以修改树的形状(虽然不能修改连通性),所以我们兴许可以用这个性质来维护一个区间。
我们不妨这样想,如果以下标为关键字,那么我们如果要维护 [lr] 这个区间时,我们先将 l1 转到根,然后再将 r+1 转到根的右子树中,这样,根的右子树的左子树就是 [lr] 这个区间,然后就可以用玄学方法维护了。
比如,维护区间旋转,我们可以打标记,然后下放时将左右子树交换。为什么交换就可以了呢?因为我们输出答案的话是中序输出,那么交换之后我们再中序的时候,根据这个不动点,我们还是可以证明是转成功的。
又比如,像线段树一样维护区间加等。我们仍以下标为关键字,然后每个点记一下子树的总的 cnt 以及他自己的 cnt ,然后就可以像一般的线段树一样下放标记。
那么这里说明一下splay维护区间,或者说序列的典型例题以及需要注意的地方.

  • Bzoj1014

题意:给你一个字符串,支持中间插入,单个字词修改。查询 x 位置的后缀与y位置后缀的 lcp

  • 题解:

静态版本就是后缀数组就完了。考虑如何动态。
首先,如果不用后缀数组,那么明显就该用二分加哈希来计算。所以我们 splay 就维护序列的哈希值就完了。

  • 一些细节的地方:

一个节点的哈希值是这个节点及其子树表示的序列(一定是连续的一段)的哈希值。
如果没有加入保护节点,那么要时刻注意在头部加入以及在尾部加入的情况。
要寻找第 k 个字符所在的节点的话应当寻找中序遍历的第k个。因为如果使用指针实现的,就只能这样。当然,如果你是数组版或者指针加数组版,当我没说。
还有一个巨大的坑点:


void Insert(int pos,char c){
    n++;
    if ( pos < n ) ...
    else ...
}

这份代码意义很明显。就是在某个位置插入一个字符。然而由于我没有设置保护节点,所以要判一下是不是在最后加入。但是,为什么是先加入n呢?这样子 pos 永远小于 n ,就会RE
正确写法:

void Insert(int pos,char c){
    if ( pos < n ) ...
    else ...
    n++;
}
  • Bzoj 3786

分析,题意什么的就不说了。这里只说一下细节。
如果有 pushdown 函数,而且你又不想像 lct 那样“暴力”,那么 pushdown 写在 splay 里面就好了。
这种维护 dfs 序的东西,你要精确的找的原来的第 k 个位置所对应的点的时候。显然不能kthfind。这时候就应该数组结合指针了。
就是说,把新建的节点全部放在数组中。

  • Bzoj 3729

注意奇偶要分清。

现在只学了这么多。大概就写到这吧,以后又学到了再填坑。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值