splay(学习笔记,更新中)

平衡树

  • splay(代码适中,十分灵活)
  • 红黑树(效率高,代码长)
  • treap(很好写,效率高,有局限性)/ SBT
  • AVL
  • B-树(多叉树,硬盘实现)
  • B + − B^{+}- B+数(多叉树,硬盘实现)

Splay

Splay是一种二叉查找树,它通过不断将某个点旋转到根节点,使得整颗树仍然保持二叉树的性质,并且保持平衡而不退化为链。它由Daniel Sleator 和Robert Tarjan发明(可以处理区间问题)

一、定义:

平衡二叉树( v a l l < v a l u < v a l r val_l<val_u<val_r vall<valu<valr,左,根,右)

二、节点维护信息

rttotfa[i]ch【i】【0/1】val[i]cnt[i]sz[i]
根节点编号节点个数父亲左右儿子编号节点权值权值出现次数子树大小
struct Node{
    int ch[2], p, v, cnt;
    //p父亲,v编号
    int siz, flag;
    //siz子树大小,用来寻找第k大数
    //flag懒惰标记
    void init(int _v, int _p){
        v = _v, p = _p;
        siz = 1;
        cnt = 1;
        ch[0] = ch[1] = 0;//没有儿子
    }
}tr[N];

三、基本操作

//void maintain(int x){ sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + cnt[x]; }
void pushup(int x){
    tr[x].siz = tr[ls].siz + tr[rs].siz + 1;
}
//bool get(int x){ return x == ch[fa[x]][1]; }
bool get(int x){
    int y = tr[x].p;
    return x == tr[y].ch[1];
}
void clear(int x){ ch[x][0] = ch[x][1] = fa[x] = val[x] = sz[x] = cnt[x] = 0; }

四、旋转操作

为了使Splay保持平衡而进行旋转操作,旋转的本质是将某个节点上移一个位置。、

旋转需要保证

  • 左旋和右旋,旋转时候不改变中序遍历
  • 受影响节点维护的信息依然正确有效。
  • root必须指向旋转后的根节点。

具体分析旋转步骤:(假设需要旋转的节点为x,其父亲为y,以右旋为例)

  1. y y y的左儿子指向 x x x的右儿子,且 x x x的右儿子(如果有的话)的父亲指向y;
ch[y][0] = ch[x][1];
fa[ch[x][1]] = y;
  1. x x x的右儿子指向 y y y,且 y y y的父亲指向 x x x
ch[x][chk^1] = y; fa[y] = x;
  1. 如果原来的 y y y还有父亲 z z z,那么把 z z z的某个儿子(原来 y y y所在的儿子位置)指向 x x x,且 x x x的父亲指向 z z z
fa[x] = z; if(z) ch[z][y==ch[z][1]] = x;
  • 综上

    在这里插入图片描述

/*
void rotate(int x){
    int y = fa[x], z = fa[y], chk = get(x);
    ch[y][chk] = ch[x][chk^1];
    if(ch[x][chk^1]) fa[ch[x]][chk^1] = y;
    ch[x][chk^1] = y;
    fa[y] = x;
    fa[x] = z;
    if(z) ch[z][y==ch[z][1]] = x;
    maintain(y);//pushup()
    maintain(x);//pushup()
}
*/
void rotate(int x){
    int y = tr[x].p, z = tr[y].p;
    int k = get(x);
    //int k = tr[y].ch[1] == x; 
    // k=0表示x是y的左儿子;k=1表示x是y的右儿子
    tr[z].ch[get(y)] = x, tr[x].p = z;
    //tr[z].ch[tr[z].ch[1] == y] = x, tr[x].p = z;
    tr[y].ch[k] = tr[x].ch[k^1], tr[tr[x].ch[k^1]].p = y;
    tr[x].ch[k^1] = y, tr[y].p = x;
    pushup(y), pushup(x);
}

S p l a y ( ) 操 作 Splay()操作 Splay()

  • S p l a y Splay Splay规定:每访问一个节点都要强制将其旋转到根节点。此时旋转
  • 注意:旋转后,就不再是二叉搜索树了。但是中序遍历还是。

右旋(左到右)

左旋(右到左)

每操作一个节点,都将该节点旋转到树根。(基于:这个点在后面可能被刷到)

  • 保证树的高度的时间复杂度 O ( l o g n ) O(logn) O(logn)

  • s p l a y ( x , k ) splay(x,k) splay(x,k)将点x旋转至点k下面

两种情况:4大类

/*
void splay(){
    for(int f = fa[x]; f = fa[x], f; rotate(x))
        if(fa[f]) rotate(get(x) == get(f) ? f : x);
    rt = x;
}
*/
void splay(int x, int k){
    while(tr[x].p != k){
        int y = tr[x].p, z = tr[y].p;
        if(z != k)
            if((tr[y].ch[1] == x) ^ (tr[z].ch[1] == y)) rotate(x);
            else rotate(y);
        rotate(x);
    }
    if(!k) root = x;
}

五、常用操作

查找操作

从根节点开始,左小右大(左侧都比他小,右侧都比他大)。

类似二叉平衡树的搜索

inline int find(int v){
    int x = root, res;
    while(x){
        if(tr[x].v >= v) res = x, x = ls;
        else x = rs;
    }
    splay(x,0);
    return res;
}

广义插入(插一个数)

插入操作是一个比较复杂的过程,具体步骤如下(假设插入的值为k):

  • 如果树空了,则直接插入根并退出
  • 如果当前节点的权值等于k则 增加当前节点的大小(cnt),并更新节点父节点的信息,将当前节点进行Splay操作。
  • 否则按照二叉查找树的性质向下找,找到空节点就插入即可(不要忘记Splay操作)
inline void insert(int v){
    int u = root, p = 0;
    while(u && tr[u].v != v){
        p = u;
        u = tr[u].ch[v > tr[u].v];
    }
    if(u) tr[u].cnt++;
    else {
        //@TODO
        u = ++idx;
        if(p) tr[p].ch[v > tr[p].v] = u;
   		tr[u].init(v,p);
    }
    splay(u,0);//每次操作后,把儿子转到根
}

前驱/后继操作 Next

首先要执行Find操作

把要查找的数弄到根节点

然后,以前驱为例

  • 前驱比他小,所以在左子树上,
  • 然后他的前驱是左子树中最大的值(中序遍历)
  • 所以一直跳右节点,直到没有为止
//@TODO:debug
inline int Next(int val, int f){
    //f表示查询的是前驱(0),还是后继(1)
    find(val);
    int x = root;
    if(tr[x].v > val && f) return x;//如果当前节点的值大于val并且要查找的是后继
    if(tr[x].v < val && !f) return x;//如果当前节点的值小于val并且要查找的是前驱
    x = tr[x].ch[f];
     while(tr[x].ch[f^1]) x= tr[x].ch[f^1];//要反着跳转,否则会越来越大(越来越小)
    return x;//返回位置
}

将一个序列插到y的后面

找到 y y y y y y 的后继 z z z

1)将 y y y转到根, s p l a y ( y , 0 ) splay(y,0) splay(y,0)

2)将 z z z 转到 y的下面 s p l a y ( z , y ) splay(z,y) splay(z,y) (既然是后继,那么 z z z的左子树为空)

(接到z的左儿子上) \tag{接到z的左儿子上} (z)

删除一段

1)删除区间 [ L , R ] [L,R] [L,R],先将 L − 1 L-1 L1转到根节点。

2)将 R + 1 R+1 R+1转到根节点的下面。

维护信息

1)找第k个数,size

2)懒标记…flag

pushup()//维护信息
pushdown()//标记下传

查询排名x的数 get_k(找第k大的数

//根据二叉查找树的定义,左小右大,显然可以查找
int get_k(int k){
    int x = root;
    while(true){
        pushdown(x);
        if(tr[ls].siz >= k) x =ls;
        else if(tr[ls].siz + 1 == k) return x;//return tr[x].v;
        else k -= tr[ls].siz + 1, x = rs;
    }
    return -1;
}

查询x的排名

根据二叉查找树的定义和性质,显然可以按照以下步骤查询x的排名:

  • 如果 比当前节点的权值小,向其左子树查找。
  • 如果 比当前节点的权值大,将答案加上左子树(size)和当前节点(cnt)的大小,向其右子树查找。
  • 如果 x 与当前节点的权值相同,将答案加 1 并返回。

六、题目

模板题:

在这里插入图片描述

  • 这里维护的是:不是有序序列,所以有些操作不能使用平衡二叉树来操作!!!!
//@TODO 这份还待修改
#include <iostream>
#include <cstdio>
#include <vector>
#include <map>
#include <unordered_map>
#include <set>
#include <algorithm>
#include <queue>
#include <string>
#include <cmath>
#include <cstring>
#define For(i,x,y) for(int i = (x); i <= (y); i ++ )
#define fori(i,x,y) for(int i = (x); i < (y); i ++ )
#define sz(a) (int)a.size()
#define ALL(a) a.begin(), a.end()
#define mst(x,a) memset(x,a,sizeof(x))
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define db double
#define endl '\n' 
#define debug(a) cout << #a << ": " << a << endl
using namespace std;
typedef long long LL;
typedef long long ll;
typedef unsigned long long ULL;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
typedef pair<int,int>pa;
typedef pair<ll,ll>pai;
typedef pair<db,db> pdd;
const db eps = 1e-6;
const db pi = acos(-1.0);

template<typename T1, typename T2> void ckmin(T1 &a, T2 b) { if (a > b) a = b; }
template<typename T1, typename T2> void ckmax(T1 &a, T2 b) { if (a < b) a = b; }
int read() {
    int x = 0, f = 0; char ch = getchar();
    while (!isdigit(ch)) f |= ch == '-', ch = getchar();
    while (isdigit(ch)) x = 10 * x + ch - '0', ch = getchar();
    return f ? -x : x;
}
template<typename T> void print(T x) {
    if (x < 0) putchar('-'), x = -x;
    if (x >= 10) print(x / 10);
    putchar(x % 10 + '0');
}
template<typename T> void print(T x, char let) {
    print(x), putchar(let);
}
template<class T> bool uin(T &a, T b) { return a > b ? (a = b, true) : false; }
template<class T> bool uax(T &a, T b) { return a < b ? (a = b, true) : false; }

const int N = 100000 + 6;

int n, m;
struct Node{
    int ch[2], p, v;
    //p父亲,v编号
    int siz, flag;
    //siz子树大小,flag懒惰标记
    void init(int _v, int _p){
        v = _v, p = _p;
        siz = 1;
    }
}tr[N];
int root, idx;
#define ls tr[x].ch[0]
#define rs tr[x].ch[1]
void pushup(int x){
    tr[x].siz = tr[ls].siz + tr[rs].siz + 1;
}
bool get(int x){
    int y = tr[x].p;
    return x == tr[y].ch[1];
}
void pushdown(int x){
    if(tr[x].flag){
        swap(ls,rs);
        tr[ls].flag ^= 1;
        tr[rs].flag ^= 1;
        tr[x].flag = 0;
    }
}
void rotate(int x){
    int y = tr[x].p, z = tr[y].p;
    int k = get(x);
    //int k = tr[y].ch[1] == x; 
    // k=0表示x是y的左儿子;k=1表示x是y的右儿子
    tr[z].ch[get(y)] = x, tr[x].p = z;
    //tr[z].ch[tr[z].ch[1] == y] = x, tr[x].p = z;
    tr[y].ch[k] = tr[x].ch[k^1], tr[tr[x].ch[k^1]].p = y;
    tr[x].ch[k^1] = y, tr[y].p = x;
    pushup(y), pushup(x);
}
void splay(int x, int k){
    while(tr[x].p != k){
        int y = tr[x].p, z = tr[y].p;
        if(z != k)
            if((tr[y].ch[1] == x) ^ (tr[z].ch[1] == y)) rotate(x);
            else rotate(y);
        rotate(x);
    }
    if(!k) root = x;
}
void insert(int v){
    int u = root, p =0;
    while(u) p = u, u = tr[u].ch[v > tr[u].v];
    u = ++idx;
    if(p) tr[p].ch[v > tr[p].v] = u;
    tr[u].init(v,p);
    splay(u,0);//每次操作后,把儿子转到根
}
int get_k(int k){
    int x = root;
    while(true){
        pushdown(x);
        if(tr[ls].siz >= k) x =ls;
        else if(tr[ls].siz + 1 == k) return x;
        else k -= tr[ls].siz + 1, x = rs;
    }
    return -1;
}
void output(int x){
    pushdown(x);
    if(ls) output(ls);
    if(tr[x].v >= 1 && tr[x].v <= n) printf("%d ", tr[x].v);
    if(rs) output(rs);
}
int main() {
    //ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    scanf("%d%d", &n, &m);
    For(i,0,n+1)insert(i);
    while(m--){
        int l, r;
        scanf("%d%d", &l, &r);
        l = get_k(l), r = get_k(r+2);
        splay(l,0), splay(r,l);
        int x = r;
        tr[ls].flag ^= 1;
    }
    output(root);
    return 0;
}

eg

在这里插入图片描述
在这里插入图片描述

  • 本题由于是对整个区间整体操作,所以可以取巧。

    1. 加两个哨兵 L = -INF, R = INF
    2. 每次删除

    x + d e l t a < m i n x + d e l t a < m i n x + delta < minx + delta < min x+delta<minx+delta<min

    x < m i n − d e l t a x < min - delta x<mindelta

    找 到 第 一 个 x ≥ m i n − d e l t a 找 到 第 一 个 x ≥ m i n − d e l t a 找到第一个x\ge min-delta找到第一个x\ge min-delta xmindeltaxmindelta

    [ L , R ] [ L , R ] [L,R][L,R] [L,R][L,R]

  • 这里维护的是有序序列,所以有些操作可以用平衡二叉树的操作。

#include <iostream>
#include <cstdio>
#include <vector>
#include <map>
#include <unordered_map>
#include <set>
#include <algorithm>
#include <queue>
#include <string>
#include <cmath>
#include <cstring>
#define For(i,x,y) for(int i = (x); i <= (y); i ++ )
#define fori(i,x,y) for(int i = (x); i < (y); i ++ )
#define sz(a) (int)a.size()
#define ALL(a) a.begin(), a.end()
#define mst(x,a) memset(x,a,sizeof(x))
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define db double
#define endl '\n' 
#define debug(a) cout << #a << ": " << a << endl
using namespace std;
typedef long long LL;
typedef long long ll;
typedef unsigned long long ULL;
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
typedef pair<int,int>pa;
typedef pair<ll,ll>pai;
typedef pair<db,db> pdd;
const db eps = 1e-6;
const db pi = acos(-1.0);

template<typename T1, typename T2> void ckmin(T1 &a, T2 b) { if (a > b) a = b; }
template<typename T1, typename T2> void ckmax(T1 &a, T2 b) { if (a < b) a = b; }
int read() {
    int x = 0, f = 0; char ch = getchar();
    while (!isdigit(ch)) f |= ch == '-', ch = getchar();
    while (isdigit(ch)) x = 10 * x + ch - '0', ch = getchar();
    return f ? -x : x;
}
template<typename T> void print(T x) {
    if (x < 0) putchar('-'), x = -x;
    if (x >= 10) print(x / 10);
    putchar(x % 10 + '0');
}
template<typename T> void print(T x, char let) {
    print(x), putchar(let);
}
template<class T> bool uin(T &a, T b) { return a > b ? (a = b, true) : false; }
template<class T> bool uax(T &a, T b) { return a < b ? (a = b, true) : false; }

const int N = 1e5 + 6;
const int INF = 1e9;

int n, m, delta;
struct Node{
    int ch[2], p, v;
    int siz;
    //这题的v维护的是员工的薪资,有序
    void init(int _v, int _p){
        v = _v, p =_p;
        siz = 1;
    }
}tr[N];

int root, idx;
#define ls tr[x].ch[0]
#define rs tr[x].ch[1]
void pushup(int x){
    tr[x].siz = tr[ls].siz + tr[rs].siz + 1;
}
bool get(int x){
    int y = tr[x].p;
    return x == tr[y].ch[1];
}
void rotate(int x){
    int y = tr[x].p, z = tr[y].p;
    int k = get(x);
    //int k = tr[y].ch[1] == x; 
    // k=0表示x是y的左儿子;k=1表示x是y的右儿子
    tr[z].ch[get(y)] = x, tr[x].p = z;
    //tr[z].ch[tr[z].ch[1] == y] = x, tr[x].p = z;
    tr[y].ch[k] = tr[x].ch[k^1], tr[tr[x].ch[k^1]].p = y;
    tr[x].ch[k^1] = y, tr[y].p = x;
    pushup(y), pushup(x);
}
void splay(int x, int k){
    while(tr[x].p != k){
        int y = tr[x].p, z = tr[y].p;
        if(z != k)
            if((tr[y].ch[1] == x) ^ (tr[z].ch[1] == y)) rotate(x);
            else rotate(y);
        rotate(x);
    }
    if(!k) root = x;
}
int insert(int v){
    int u = root, p = 0;
    while(u) p = u, u = tr[u].ch[v > tr[u].v];
    u = ++idx;
    if(p) tr[p].ch[v > tr[p].v] = u;
    tr[u].init(v,p);
    splay(u, 0);
    return u;
}
int find(int v){
    int x = root, res;
    while(x){
        if(tr[x].v >= v) res = x, x = ls;
        else x = rs;
    }
    return res;
}
int get_k(int k){
    int x = root;
    while(x){
        if(tr[ls].siz >= k) x = ls;
        else if(tr[ls].siz + 1 == k) return tr[x].v;
        else k -= tr[ls].siz + 1, x = rs;
    }
    return -1;
}
int main() {
   // ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    scanf("%d%d", &n, &m);
    int L = insert(-INF), R = insert(INF);

    int tot = 0;
    while(n -- ){
        char op[2];
        int k;
        scanf("%s%d", op, &k);
        if(*op == 'I'){
            if(k >= m) k -= delta, insert(k), tot++;
        }else if(*op == 'A') delta += k;
        else if(*op == 'S'){
            delta -= k;
            R = find(m-delta);
            splay(R,0), splay(L,R);
            tr[L].ch[1] = 0;
            pushup(L), pushup(R);
        }else {
            if(tr[root].siz -2 < k) puts("-1");
            else printf("%d\n", get_k(tr[root].siz - k) + delta);
        }
    }
    printf("%d\n", tot - (tr[root].siz - 2));
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值