splay

(又是一个被我咕咕咕很久了的知识点)

板子题

 

一、Splay

又叫伸展树,分裂树。是一种二叉排序/查找/搜索树。

Splay是一种平衡二叉树,及优化后的二叉查找树。靠伸展操作splay可以自我调整,使得提升效率

 

二、二叉排序树的性质

或者是一颗空树

或者具有以下性质:

  • 若左子树不空,则左子树上所有节点的值均小于或等于它的根节点的值
  • 若柚子树不空,则右子树上所有节点的值均大于或等于它的根节点的值
  • 左右子树也分别为二叉排序树

同样的序列,因为排序不同,可能会生成不同的二叉排序树,查找效率性就不一定了。如果傻叉排序树退化成一条链,效率就很低。

 

三、变量定义

ch[  ][ 2 ] : ch[ x ][ 0 ]代表x的左儿子,ch[ x ][ 1 ]代表x的右儿子

val[  ] : val[ x ]代表x存储的值

cnt[  ] : cnt[ x ]代表x存储的重复权值的个数

fa[  ] : fa[ x ]代表x的父节点

siz[  ] : siz[ x ]代表x子树下的储存的权值数(包括重复权值,也包括自己)

 

四、支持的操作

查找第k大的数

查找一个数的排名

查找一个数的前驱

查找一个数的后继

区间翻转

---------------------------------------------------------------------------------

chk:查询一个节点是其父节点的左儿子还是右儿子

pushup:更新siz数组的值

rotate:旋转保持平衡

splay:伸展,将一个节点一直rotate到制定节点的儿子

find:将最大的小于等于给定x的数所在的节点splay到根

insert:插入

kth:查询第k大的数

rank:查找一个数的排名

pre:查找一个数的前驱

succ:查找一个数的后继

remove:删除一个数

reserve:区间翻转

pushdown:将区间标记下传

 

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

inline int read()
{
    int sum = 0,p = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
            p = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        (sum *= 10) += ch - '0';
        ch = getchar();
    }
    return sum * p;
}

const int maxn = 1e5 + 10;
int n,m,root,ncnt;
int ch[maxn][2],fa[maxn],val[maxn],cnt[maxn],siz[maxn],lazy[maxn];
//ch[][0]左儿子,ch[][1]右儿子,fa[]父节点,val[]节点的权值,cnt[]本节点贮存的大小,siz[]子树大小(包括本节点的大小),lazy[]翻转标记

bool chk(int o)//判断一个节点是他父节点的左儿子还是右儿子
{
    return ch[fa[o]][1] == o;
}

//子树大小重新计算
void pushup(int o)
{
    siz[o] = siz[ch[o][0]] + siz[ch[o][1]] + cnt[o];
}

//标记下传
void pushdown(int o)
{
    if(lazy[o])
    {
        swap(ch[o][0],ch[o][1]);
        lazy[ch[o][0]] ^= 1;
        lazy[ch[o][1]] ^= 1;
        lazy[o] = 0;
    }
}

//旋转
void rotate(int x)
{
    int y = fa[x],z = fa[y];
    int opt = chk(x),w = ch[x][opt ^ 1];
    ch[y][opt] = w;
    fa[w] = y;
    ch[z][chk(y)] = x;
    fa[x] = z;
    ch[x][opt ^ 1] = y;
    fa[y] = x;
    pushup(y),pushup(x);
}

//伸展
void  splay(int x,int goal = 0)
{
    while(fa[x] != goal)
    {
        int y = fa[x],z = fa[y];
        if(z != goal)
        {
            if(chk(x) == chk(y))
                rotate(y);
            else
                rotate(x);
        }
        rotate(x);
    }
    if(!goal)
        root = x;
}

//插入
void insert(int o)
{
    int cur = root,p = 0;
    while(cur && val[cur] != o)
    {
        p = cur;
        cur = ch[cur][o > val[cur]];
    }
    if(cur)
    {
        cnt[cur]++;
    }
    else
    {
        cur = ++ncnt;
        if(p)
            ch[p][o > val[p]] = cur;
        ch[cur][0] = ch[cur][1] = 0;
        fa[cur] = p;
        val[cur] = o;
        cnt[cur] = siz[cur] = 1;
    }
    splay(cur);
}

//找到权值为o的节点,并放到根节点
void find(int o)
{
    int cur = root;
    while(ch[cur][o > val[cur]] && val[cur] != o)
    {
        cur = ch[cur][o > val[cur]];
    }
    splay(cur);
}

//求第o大的数
int kth(int o)
{
    int cur = root;
    while(1)
    {
        pushdown(cur);
        if(ch[cur][0] && o <= siz[ch[cur][0]])
            cur = ch[cur][0];
        else if(o > (siz[ch[cur][0]] + cnt[cur]))
        {
            o -= (siz[ch[cur][0]] + cnt[cur]);
            cur = ch[cur][1];
        }
        else
            return cur;
    }
}

//翻转
void reverse(int l,int r)
{
    int x = kth(l),y = kth(r + 2);
    splay(x);
    splay(y,x);
    lazy[ch[y][0]] ^= 1;
}

//求前驱
int pre(int o)
{
    find(o);
    if(val[root] < o)
        return root;
    int cur = ch[root][0];
    while(ch[cur][1])
        cur = ch[cur][1];
    return cur;
}

//求后继
int succ(int x)
{
    find(x);
    if(val[root] > x)
        return root;
    int cur = ch[root][1];
    while(ch[cur][0])
        cur = ch[cur][0];
    return cur;
}

//删除
void remove(int o)
{
    int last = pre(o),nxt = succ(o);
    splay(last);
    splay(nxt,last);
    int del = ch[nxt][0];
    if(cnt[del] > 1)
    {
        cnt[del]--;
        splay(del);
    }
    else
        ch[nxt][0] = 0;
} 

//查询rank
void rank(int o)
{
    find(o);
    printf("%d\n",siz[ch[root][0]]);
} 

//输出
void output(int x)
{
    pushdown(x);
    if(ch[x][0])
        output(ch[x][0]);
    if(val[x] && val[x] <= n)
        printf("%d ",val[x]);
    if(ch[x][1])
        output(ch[x][1]);
}

int main()
{
    n = read(),m = read();
    for(int i = 0; i<= n + 1; i++)
        insert(i);
    while(m--)
    {
        int x = read(),y = read();
        reverse(x,y);
    }
    output(root);
}
View Code

 

(写(chao)代码时犯的沙雕错误:reverse函数定义变量名的时候少打了一个r,而在主函数调用的时候用的还是reverse,由于reverse时stl里的,导致编译不了,蹦出长长的看不懂的提示)

posted @ 2019-07-24 14:28  darrrr  阅读(314)  评论(0编辑  收藏  举报