bzoj 3224: Tyvj 1728 普通平衡树

本文介绍了一种普通平衡树数据结构的实现方法,该结构能够高效地进行插入、删除、查询等操作,并通过具体实例展示了其在解决特定问题时的应用。

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

3224: Tyvj 1728 普通平衡树

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 15580   Solved: 6786
[ Submit][ Status][ Discuss]

Description

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
1. 插入x数
2. 删除x数(若有多个相同的数,因只删除一个)
3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
4. 查询排名为x的数
5. 求x的前驱(前驱定义为小于x,且最大的数)
6. 求x的后继(后继定义为大于x,且最小的数)

Input

第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6)

Output

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

Sample Input

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

Sample Output

106465
84185
492737

HINT

1.n的数据范围:n<=100000

2.每个数的数据范围:[-2e9,2e9]

这个题是维护所有数是有序的, 每个rt其实就是这个数的大小所在位置,每个rt左面都比他小, 右面都比他大, 有相同的,他会缩到一个节点, cnt代表这个值的个数。


#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 7;
const int INF = 1e9;
int n, m;
int ch[maxn][2]; //0做孩子, 1右孩子
int f[maxn]; //每个节点的父亲
int sz[maxn]; //每个节点为根子树的大小
int val[maxn]; //这个节点所表示的值
int cnt[maxn]; //这个节点所表示值的数量
int mi[maxn]; //这个节点子树的最小值
int rev[maxn]; //反转标记
int lazy[maxn]; //延迟标记
int root;  // splay的根
int tot; //树所有的节点数量
void newnode(int rt, int v, int fa)
{
    f[rt] = fa;
    val[rt] = v;
    sz[rt] = cnt[rt] = 1;
    ch[rt][0] = ch[rt][1] = 0;
}
void delnode(int &rt) //删除之后, 把相关信息抹掉
{
    f[rt] = val[rt] = sz[rt] = cnt[rt] = 0;
    ch[rt][0] = ch[rt][1] = rt = 0;
}
void pushup(int x)
{
    if(x)
        sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + cnt[x]; //计算这个节点为根子树的节点个数
}
//void pushdown(int x)
//{
//
//}
void Rotate(int x, int k) // k = 0左旋, k = 1右旋
{
    int y = f[x];int z = f[y];
//    pushdown(y); pushdown(x);
    ch[y][!k] = ch[x][k];
    if(ch[x][k]) f[ch[x][k]] = y;
    f[x] = z;
    if(z) ch[z][ch[z][1]==y] = x;
    f[y] = x; ch[x][k] = y;
    pushup(y), pushup(x);
}
void splay(int x, int goal)
{
//    pushdown(x);
    while(f[x] != goal)
    {
        int y = f[x];
         //在这里下传翻转标记,在rotate里下传标记可能会使树形改变导致旋转出错
//        pushdown(z); pushdown(y); pushdown(x);
        if(f[y] == goal) Rotate(x, ch[y][0] == x);
        else
        {
            int p = ch[f[y]][0] == y;
            if(ch[y][p] == x) Rotate(x, !p), Rotate(x, p);
            else Rotate(y, p), Rotate(x, p);
        }
    }
    pushup(x);
    if(goal == 0) root = x;
}
//以x为根的子树 的极值点  0 极小 1 极大
int extreme(int x,int k)
{
    while(ch[x][k]) x = ch[x][k];
    splay(x, 0);
    return x;
}
//以x为根的子树 第k个数的位置
int kth(int x, int k)
{
//    pushdown(x);
    if(sz[ch[x][0]]+1 <= k && k <= sz[ch[x][0]]+cnt[x]) return x;
    else if(sz[ch[x][0]] >= k) return kth(ch[x][0], k);
    else return kth(ch[x][1], k-sz[ch[x][0]]-cnt[x]);
}
//查找
int Search(int rt, int x)
{
//    cout << rt << endl;
    if(ch[rt][0] && val[rt] > x) return Search(ch[rt][0], x);
    else if(ch[rt][1] && val[rt] < x) return Search(ch[rt][1], x);
    return rt;
}
//前驱:小于x的最大的数
int prec(int x)
{
    int k = Search(root, x);
    splay(k, 0);
    if(val[k] < x) return k;
    return extreme(ch[k][0], 1);
}
//后继:大于x的最小的数
int sufc(int x)
{
    int k = Search(root, x);
    splay(k, 0);
    if(val[k] > x) return k;
    return extreme(ch[k][1], 0);
}
//
int Rank(int x)
{
    int k = Search(root, x);
    splay(k, 0);
    return sz[ch[root][0]]+1;
}
//按照二叉树性质插入x
void Insert(int x)
{
    int y = Search(root, x), k = -1;
    if(val[y] == x)
    {
        cnt[y]++;
        sz[y]++;
        for(int yy = y; yy; yy = f[yy]) pushup(yy);
    }
    else
    {
        int p = prec(x), s = sufc(x);
        splay(p, 0); splay(s, p);
        newnode(++tot, x, ch[root][1]);
        ch[ch[root][1]][0] = tot;
        for(int z = ch[root][1]; z; z = f[z]) pushup(z);
    }
    splay(y, 0);
}
void Delete(int x) //删除值为x的数
{
//    cout << 1 <<endl;
    int y = Search(root, x);
    if(val[y] != x) return ;
    if(cnt[y] > 1)  //因为一个一个删
    {
        cnt[y]--;
        sz[y]--;
        for(int yy = y; yy; yy = f[yy]) pushup(yy);
    }
    else if(ch[y][0] == 0 || ch[y][1] == 0)
    {
        int z = f[y];
        ch[z][ch[z][1]==y] = ch[y][ch[y][0]==0];
        f[ch[y][ch[y][0]==0]] = z; delnode(y);
        for(int yy = z; yy; yy = f[yy]) pushup(yy);
    }
    else
    {
        int p = prec(x), s = sufc(x);
        splay(p, 0); splay(s, p);
        ch[ch[root][1]][0] = 0;
        delnode(ch[ch[root][1]][0]);
        for(int yy = s; yy; yy = f[yy]) pushup(yy);
    }
}
void init()
{
    root = 1;
    tot = 0;
    newnode(++tot, -INF, 0);
    newnode(++tot, INF, root);
    ch[root][1] = tot;
}
int main()
{
    while(~scanf("%d", &n))
    {
        init();
        for(int op, x ; n--;)
        {
            scanf("%d", &op);
            scanf("%d", &x);
            if(op == 1) Insert(x);
            else if(op == 2) Delete(x);
            else if(op == 3) printf("%d\n", Rank(x)-1);
            else if(op == 4) printf("%d\n", val[kth(root, x+1)]);
            else if(op == 5) printf("%d\n", val[prec(x)]);
            else printf("%d\n", val[sufc(x)]);
        }
    }
    return 0;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值