【Splay树】

伸展树英语:Splay Tree)是一种二叉查找树,它能在O(log n)内完成插入、查找和删除操作。它是由丹尼尔·斯立特Daniel Sleator)和罗伯特·塔扬在1985年发明的[1]

在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置。于是想到设计一个简单方法, 在每次查找之后对树进行重构,把被查找的条目搬移到离树根近一些的地方。伸展树应运而生。伸展树是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去。

它的优势在于不需要记录用于平衡树的冗余信息。

                                                               ——来自维基百科

几个常用操作:

其实也不一定对。。。

rotate操作

void rotate(int x,int &k) 
{
    int y = f[x], z = f[y], l, r;
    if (tr[y][0] == x) l = 0; else l = 1;
    r = l ^ 1;
    if (y == k) k = x;
    else
    {
        if (tr[z][0] == y) tr[z][0] = x;
            else tr[z][1] = x;
    }
    f[x] = z; f[y] = x; f[tr[x][r]] = y;
    tr[y][l] = tr[x][r]; tr[x][r] = y;    
}
rotate

把x向上旋转一个

r = l ^ 1; 01取反

splay操作

void splay(int x,int &k)
{
    int y,z;
    while (x != k)
    {
        y = f[x]; z = f[y];
        if (y != k)
        {
            if ((tr[z][0] == y)^(tr[y][0] == x)) rotate(x,k);
                else rotate(y,k);
        }
        rotate(x,k);
    }
}
splay

把x转到k的位置
y !=k 至少要旋转两次,否则直接旋转一次x就行了
(tr[z][0] == y)^(tr[y][0] == x) 判断在不在一条直线上
  如果是的话,先旋转父节点,再旋转自己;否则先旋转自己,再旋转父节点(就是旋转两次自己)
因为调用了rotate,在rotate中k的值改变了,所以在这里也要加&

插入元素

void ins(int &k,int x,int last)
{
    if (k == 0)
    {
        size ++; k = size; num[k] = x; f[k] = last; splay(k,rt); return;
    }
    if (x < num[k]) ins(tr[k][0],x,k);    else ins(tr[k][1],x,k);
}
insert

插入一个元素
操作完成后k的值会变成size,size会转到根,所以k的值要返回给rt,所以要加上&

删除元素

void del(int x)
{
    splay(x,rt);
    if (tr[x][0] * tr[x][1] == 0)
        rt = tr[x][0] + tr[x][1];
    else
    {
        int k = tr[x][1];
        while (tr[k][0]) k = tr[k][0];
        tr[k][0] = tr[x][0]; f[tr[x][0]] = k;
        rt = tr[x][1];
    }
    f[rt] = 0;
}
delete

删除编号为x的结点
先旋转到根,如果只有一棵子树,直接删除,让子树的根作为根节点
否则寻找右子树中最小的那个点,作为根节点

寻找前驱

void ask_before(int k,int x)
{
    if (k == 0) return;
    if (num[k] <= x)
    {
        t1 = k;
        ask_before(tr[k][1],x);
    }
    else ask_before(tr[k][0],x);
}
ask_before

寻找后继

void ask_after(int k,int x)
{
    if (k == 0) return;
    if (num[k] >= x)
    {
        t2 = k;
        ask_after(tr[k][0],x);
    }
    else ask_after(tr[k][1],x);
}
ask_after

建树

void build(int l,int r,int f)
{
    if (l > r) return;
    int mid = (l+r) >> 1, now = id[mid], last = id[f];
    if (l == r)
    {
        size[l] = 1; tag[l] = rev[l] = 0;
        fa[l] = last;
        if (l < f) c[last][0] = now; else c[last][1] = now;
        return;
    }
    build(l,mid-1,mid); build(mid+1,r,mid);
    fa[now] = last; updata(now);
    if (mid < f) c[last][0] = now; else c[last][1] = now;
}
build

id是编号,size是大小

找第rk大的元素

int find(int &k,int rk)
{
    pushdown(k);
    int l = c[k][0], r = c[k][1];
    if (size[l]+1 == rk) return k;
    if (size[l] >= rk) return find(l,rk);
    return find(r,rk-size[l]-1);
}
找第rk大的元素

区间+(标记)

void add(int l,int r,int v) 
{
    int x = find(rt,l), y = find(rt,r+2); 
    splay(x,rt); splay(y,c[x][1]);
    int z = c[y][0];
    tag[z] += v; mx[z] += v; w[z] += v;
}
add

翻转(标记)

void rever(int l,int r)
{
    int x = find(rt,l), y = find(rt,r+2);
    splay(x,rt); splay(y,c[x][1]);
    int z = c[y][0];
    rev[z] ^= 1;
}
rever

查询

int solvemx(int l,int r)
{
    int x = find(rt,l), y = find(rt,r+2);
    splay(x,rt); splay(y,c[x][1]); 
    int z = c[y][0];
    return mx[z];
}
query

更新

void updata(int x)
{
    int l = c[x][0], r = c[x][1];
    size[x] = size[l] + size[r] +1;
    mx[x] = max(mx[l],mx[r]);
    mx[x] = max(mx[x],w[x]);
}
updata

下传标记

void pushdown(int x)
{
    int l = c[x][0], r = c[x][1];
    if (rev[x])
    {
        rev[l] ^= 1; rev[r] ^= 1;
        rev[x] = 0;
        swap(c[x][0],c[x][1]);
    }
    if (tag[x])
    {
        if (l) tag[l] += tag[x], mx[l] += tag[x], w[l] += tag[x];
        if (r) tag[r] += tag[x], mx[r] += tag[x], w[r] += tag[x];
        tag[x] = 0;
    }
}
pushdown
原文地址:https://www.cnblogs.com/liumengyue/p/5218473.html