码迷,mamicode.com
首页 > 其他好文 > 详细

Splay树模版

时间:2021-02-01 11:44:23      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:class   namespace   max   出现   display   排名   scan   ota   void   

emmm....查了半天的错,原来是read()函数的问题,气skr人

splay树真快乐,好多实现的模版,总算找到一个容易的

老规矩,模版链接:https://blog.csdn.net/Emm_Titan/article/details/103910330

题目链接:https://www.luogu.com.cn/record/45785203

自己用数组实现+自己的救济理解

技术图片
#include<iostream>
using namespace std;
#define INF 0x7f7f7f7f
typedef long long ll;
const int maxn = 1e5 + 100;
inline int read()
{
    int s = 0, w = 1; char ch = getchar();
    while (ch < 0 || ch > 9) { if (ch == -) w = -1; ch = getchar(); }
    while (ch >= 0 && ch <= 9) s = s * 10 + ch - 0, ch = getchar();
    return s * w;
}
int tree[maxn][2], siz[maxn], cnt[maxn], pre[maxn], val[maxn];
//siz:子节点个数(包括自己),cnt:自己出现的次数,pre:父节点,val:节点权值,tree[x][0]=0表示x无左孩子,右孩子同理
int tot=0, root=0;
void push_up(int x) {//每次旋转都需要更新节点左右子树个数
    siz[x] = siz[tree[x][0]] + siz[tree[x][1]] + cnt[x];
}
void rotate(int x) {//两种情况,如果是父节点的右孩子(f=0),就左旋(父节点变x左节点)和如果是左孩子(f=1),就右旋(父节点变x右节点);
    int y = pre[x], z = pre[y], f = (tree[y][0] == x);
    //需要更新两条边,f^1就是取反,1变0,0变1,可以再草稿本上画一下
    //1:与父节点相连的那条边,父节点某孩子替换为x的某孩子节点,原本的x孩子节点替换为父节点
    tree[y][f ^ 1] = tree[x][f];
    pre[tree[x][f]] = y;
    tree[x][f] = y;
    pre[y] = x;
    //2:父节点与祖父节点相连的那条边,祖父节点不再接父节点而是x
    if (z) {tree[z][tree[z][1] == y] = x;}
    pre[x] = z;
    push_up(y);//先更新y再更新x,y已经是x的孩子节点了,即先更新孩子再更新自己
    push_up(x);
}
void splay(int x,int top) {//将x旋转到为top的孩子,一般把top设置为0,即没有父亲,则旋转到根节点
    //三种情况
    while (pre[x] != top) {
        int y = pre[x], z = pre[y];
        if (z != top) {//1如果祖父已经是终止节点top,直接把x旋转到父节点即可,不进入判断
            //如果祖父不是终止节点,就把x旋转到祖父节点,需要判断三个节点是否共线(用异或,比较巧,所以可以当模板用)
            if (((tree[z][0] == y) ^ (tree[y][0] == x))== 1) {//如果不共线,就旋转x两次到祖父节点
                rotate(x);
            }
            else {//如果共线就先旋转y,再旋转x
                rotate(y);
            }
            //为什么不不去判断是否共线,直接旋转两次x呢...百度了一下,没找到,我猜因为先旋转y就让树更平衡了,更接近平衡树
        }
        rotate(x);
    }
    if (!top)root = x;
}
//后面就没啥技巧了,看源码基本都能看懂
void insert(int w) {//插入权值为w的值
    int fa = 0, p = root;//先用中序遍历方法查找插入的位置
    while (p && val[p] != w) {//终止条件为要么找到权值为w的点,要么找到其插入的父节点
        fa = p;
        p = tree[p][w>val[p]];
    }
    if (p) {//如果已经存在,+1即可
        cnt[p]++;
    }
    else {//不存在就申请节点,把数据都设置为初始值
        p = ++tot;
        tree[p][0] = tree[p][1] = 0;
        siz[p] = cnt[p] = 1;
        val[p] = w;
        if (fa) {
            tree[fa][w > val[fa]] = p;
        }
        pre[p] = fa;
    }
    splay(p, 0);//注意要把刚插入的值旋转到根,因为你刚更新了个数啊什么的,需要push_up去更新上面的点的数据
}
void find(int w) {//找到最接近w的值把他旋转到根节点
    if (!root)return;
    int p = root;
    while (tree[p][w >val[p]] && val[p] != w) {
        p = tree[p][w>val[p]];
    }
    splay(p, 0);
}
int find_kth(int k){//查找排名为k的节点
    int p = root;
    if (siz[p] < k)return -1;
    while (1) {
        int l = tree[p][0], r = tree[p][1];
        if (k<=siz[l])p = l;//这里要超级小心啊,判断要想清楚
        else if (k<=cnt[p] + siz[l])return p;
        else {
            k -= (siz[l] + cnt[p]);
            p = r;
        }
    }
}
int pre_suf(int w,int f) {//找到前驱或者后继,f为0前驱,1为后继
    find(w);//先将与w最接近的节点旋转到根
    int p = root;
    if (val[p] > w && f) {//如果p节点权值比w>而且是查后继,那么p就刚好是结果
        return p;
    }
    if (val[p] < w && !f) {//同理
        return p;
    }
    //都不是的话,找后继就是右子树最小节点(右子树最左的那个节点),找前驱就是左子树最大节点(左子树最右的那个节点)
    p = tree[p][f];
    if (!p)return 0;
    while (tree[p][f ^ 1]) {
        p = tree[p][f ^ 1];
    }
    return p;
}
void del(int x) {//删除权值为x的节点
    //先找到x的前驱节点与后继节点,然后把前驱节点旋转到根,后继节点旋转到前驱节点的右孩子位置
    //于是乎权值为x的节点比在后继节点的左子树位置
    //因为pre<x<suf然后suf又刚好在pre的右孩子位置,然后前驱和后继又都是最接近x的,所以....必定在,除非树里面没有这个节点x
    //这个是我看到模板里面实现最简单的了
    int pre = pre_suf(x, 0), suf = pre_suf(x, 1);
    splay(pre, 0);
    splay(suf, pre);//pre可能为0
    int p = tree[suf][0];
    if (cnt[p] > 1) {
        cnt[p]--;
        splay(p, 0);//每次涉及到修改的都要splay啊!!!!
    }
    else {
        tree[suf][0] = 0;
    }
}
int main()
{
    //freopen("test.txt", "r", stdin);
    insert(INF);
    insert(-INF);
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        int cmd, x;
        scanf("%d%d", &cmd, &x);
        if (cmd == 1) {
            insert(x);
        }
        else if (cmd == 2) {
            del(x);
        }
        else if (cmd == 3) {
            find(x);//查找w的rank就把他旋转到根,左孩子个数就是rank,本来要+1的,但是由于有哨兵-INT的存在,就不用啦
            printf("%d\n", siz[tree[root][0]]);
        }
        else if (cmd == 4) {
            printf("%d\n", val[find_kth(x + 1)]);
        }
        else if (cmd == 5) {
            printf("%d\n", val[pre_suf(x, 0)]);
        }
        else {
            printf("%d\n", val[pre_suf(x, 1)]);
        }
    }
    return 0;
}
View Code

 

Splay树模版

标签:class   namespace   max   出现   display   排名   scan   ota   void   

原文地址:https://www.cnblogs.com/MYMYACMer/p/14348972.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!