伸展树(splay)入门学习(一) (洛谷 P3369 【模板】普通平衡树)

本文详细介绍了一种高效的数据结构——伸展树,用于维护数值集合,并提供了插入、删除、查询排名、查找指定排名的数、求前驱和后继等操作的实现。通过具体的代码示例,展示了伸展树的强大功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入 xx 数
  2. 删除 xx 数(若有多个相同的数,因只删除一个)
  3. 查询 xx 数的排名(排名定义为比当前数小的数的个数 +1 。若有多个相同的数,因输出最小的排名)
  4. 查询排名为 xx 的数
  5. 求 xx 的前驱(前驱定义为小于 xx ,且最大的数)
  6. 求 xx 的后继(后继定义为大于 xx ,且最小的数)

输入输出格式

输入格式:

 

第一行为 nn ,表示操作的个数,下面 nn 行每行有两个数 optopt 和 xx , optopt 表示操作的序号( 1 \leq opt \leq 61≤opt≤6 )

 

输出格式:

 

对于操作 3,4,5,63,4,5,6 每行输出一个数,表示对应答案

 

输入输出样例

输入样例#1: 

10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

输出样例#1: 

106465
84185
492737

说明

时空限制:1000ms,128M

1.n的数据范围: n \leq 100000n≤100000

2.每个数的数据范围: [-{10}^7, {10}^7][−107,107]

 

学习了下伸展树,伸展树的功能真的很强大,直接粘板子就行,板子里的注释写的挺详细了

​
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
struct tree
{
    int father,lch,rch,val,cnt,size; //父节点、左儿子、右儿子、值、该值出现次数、子树大小
}stree[1000005];
int root,tot; // 根节点,树的大小
inline void clearr(int x)
{
    stree[x].lch = stree[x].rch = stree[x].father = stree[x].size = stree[x].cnt = stree[x].val = 0;
}
inline bool get(int x) // 判断是父亲的左儿子还是右儿子
{
    return stree[ stree[x].father ].rch == x;
}
inline void update(int x) // 更新子树大小
{
    if (x)
    {
        stree[x].size = stree[x].cnt;   // 该值出现次数  + 左右子树大小
        if (stree[x].lch)
            stree[x].size += stree[ stree[x].lch ].size;
        if (stree[x].rch)
            stree[x].size += stree[ stree[x].rch ].size;
    }
}
inline void rotate(int x)//旋转
{
    int fa,oldf,whichx;
    fa = stree[x].father;  // x的父节点
    oldf = stree[fa].father;  // x父节点的父节点
    whichx = get(x); // 判断x是父节点左儿子还是右儿子
    if (whichx) //x是父节点的右儿子
    {
        stree[fa].rch = stree[x].lch;
        stree[ stree[fa].rch ].father = fa; // 将x的左儿子变为x父节点的右儿子
        stree[x].lch = fa;
        stree[fa].father = x;
        stree[x].father = oldf; // 将x父节点变为x左儿子
    }
    else    // 同上
    {
        stree[fa].lch = stree[x].rch;
        stree[ stree[fa].lch ].father = fa;
        stree[x].rch = fa;
        stree[fa].father = x;
        stree[x].father = oldf;
    }
    if (oldf) // 如果有x的父节点有父节点,则更新祖父节点的(左/右)儿子为x
    {
        if (stree[oldf].rch == fa)
            stree[oldf].rch = x;
        else
            stree[oldf].lch = x;
    }
    update(fa);
    update(x);  // 更新x与x父节点的大小
}
inline void splay(int x, int t)//将x伸展到t的子节点
{
    int fa = stree[x].father; //将x伸展至父节点
    while (fa != t)
    {
        if (stree[fa].father) // 如果父节点有父节点
        {
            if (get(x) == get(fa)) // 一字型(需旋转父节点再旋转x)
                rotate(fa);
            else
                rotate(x); // 之字形(需旋转x两次)
        }
        rotate(x); // 旋转x
        fa = stree[x].father; //更新父节点
    }
    if (!t)
        root = x; // 更新根节点
}
inline void insert(int x) // 插入x
{
    if (!root) // 如果splay树为空
    {
        tot++;
        stree[tot].lch = stree[tot].rch = stree[tot].father = 0;
        root = tot;
        stree[tot].size = stree[tot].cnt = 1;
        stree[tot].val = x;
        return;
    }
    int u = root;
    int fa = 0;
    while (1)
    {
        if (x == stree[u].val) // 如果splay树里已经有这个值
        {
            stree[u].cnt++;
            update(u);
            update(fa);
            splay(u, 0);
            return;
        }
        fa = u;
        if (stree[u].val < x)
            u = stree[u].rch;
        else
            u = stree[u].lch;   // 找该值应该是在当前节点的左/右子树内
        if (!u) // 如果找到x应作为fa节点的左/右儿子
        {
            tot++; // 树大小+1
            stree[tot].lch = stree[tot].rch = 0;
            stree[tot].father = fa;
            stree[tot].size = stree[tot].cnt = 1;
            if (stree[fa].val < x)
                stree[fa].rch = tot;
            else
                stree[fa].lch = tot;
            stree[tot].val = x;  // 创建该节点
            update(fa); // 更新父节点大小
            splay(tot, 0); // 将当前节点伸展到根
            return;
        }
    }
}
int find(int x) //查找x为第几小的值
{
    int ans = 0;
    int u = root;
    while (1)
    {
        if (x < stree[u].val) // 如果x在当前节点的左子树内
            u = stree[u].lch;
        else // 如果x在当前节点的右子树内
        {
            if (stree[u].lch) //当前节点的左子树都比x小
                ans += stree[ stree[u].lch ].size;
            if (x == stree[u].val) // 如果x是当前节点,则找到排名
            {
                splay(u, 0); // 将x伸展到根节点
                return ans + 1;
            }
            ans += stree[u].cnt; //  当前节点比x小
            u = stree[u].rch;  //  更新当前节点
        }
    }
}
int findx(int x)//找第x小的点
{
    int u = root;
    while (1)
    {
        if (stree[u].lch && stree[ stree[u].lch ].size >= x) // 如果当前节点的左子树大小已经大于等于x,即就在当前节点的左子树内找
            u = stree[u].lch;
        else
        {
            int tmp = stree[u].cnt;
            if (stree[u].lch)
                tmp += stree[ stree[u].lch ].size;
            if (tmp >= x)   // 当前值即为要寻找的值
                return stree[u].val;
            x -= tmp;   // 已经找到最小的tmp个树
            u = stree[u].rch; // 去当前节点的有子树内找
        }
    }
}
inline int pre()//查找x的前驱(需先插入x,查找完后删除,返回值为前驱的下标)
{
    int now = stree[root].lch; //插入x时,x已经作为树的根,在x的左子树内找最大值
    while (stree[now].rch)
        now = stree[now].rch;
    return now;
}
inline int next()//查找x的后继(需先插入x,查找完后删除,返回值为后继的下标)
{
    int now = stree[root].rch; //插入x时,x已经作为树的根,在x的右子树内找最小值
    while (stree[now].lch)
        now = stree[now].lch;
    return now;
}
void deletee(int x) // 删除x
{
    p = find(x);  // 此时x已经成为根节点
    if (stree[root].cnt > 1) // 如果x出现次数超过一次
    {
        stree[root].cnt--;
        update(root);
        return;
    }
    if (!stree[root].lch && !stree[root].rch)  // 如果x没有左右子树,直接删除即可
    {
        clearr(root);
        root = 0;
        return;
    }
    if (!stree[root].lch) //如果x没有左子树,则将x的右儿子作为根,直接删除x
    {
        int tmp = root;
        root = stree[root].rch;
        stree[root].father = 0;
        clearr(tmp);
        return;
    }
    if (!stree[root].rch)//如果x没有右子树,则将x的左儿子作为根,直接删除x
    {
        int tmp = root;
        root = stree[root].lch;
        stree[root].father = 0;
        clearr(tmp);
        return;
    }
    int left = pre(); // 找x的前驱
    int tmp = root;
    splay(left, 0); // 将x的前驱伸展到根节点
    stree[root].rch = stree[tmp].rch;
    stree[ stree[tmp].rch ].father = root; // x的右儿子作为x前驱的右儿子
    clearr(tmp); // 删除x节点
    update(root); // 更新树大小
    return;
}
int main()
{
//    freopen("in.txt", "r", stdin);
    int n;
    scanf("%d", &n);
    while (n--)
    {
        int f,x;
        scanf("%d%d", &f, &x);
        if (f == 1)
            insert(x);
        if (f == 2)
            deletee(x);
        if (f == 3)
            printf("%d\n", find(x));
        if (f == 4)
            printf("%d\n", findx(x));
        if (f == 5)
        {
            insert(x);
            printf("%d\n", stree[pre()].val);
            deletee(x);
        }
        if (f == 6)
        {
            insert(x);
            printf("%d\n", stree[next()].val);
            deletee(x);
        }
    }
    return 0;
}

​

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值