数据结构之 BST 手写treap

AcWing 253. 普通平衡树

题目描述

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

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

注意: 数据保证查询的结果一定存在。

输入格式

第一行为 n,表示操作的个数。

接下来 n 行每行有两个数 opt 和 x,opt 表示操作的序号( 1 ≤ o p t ≤ 6 1≤opt≤6 1opt6)。

输出格式

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

数据范围

1 ≤ n ≤ 100000 1≤n≤100000 1n100000,所有数均在 − 1 0 7 −10^7 107 1 0 7 10^7 107 内。

输入样例:
8
1 10
1 20
1 30
3 20
4 2
2 10
5 25
6 -1
输出样例:
2
20
20
20
#include<bits/stdc++.h>
#include <unordered_map>
using namespace std;
template<class...Args>
void debug(Args... args) {//Parameter pack
    auto tmp = { (cout << args << ' ', 0)... };
    cout << "\n";
}
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, ll>pll;
typedef pair<int, int>pii;
const ll N = 1e6 + 5;
const ll MOD = 998244353;
const ll INF = 0x7fffffff;

struct Node{
    int l, r;//左右子树
    int key;//值
    int val;//随机分配的值
    int cnt;//有多少个
    int size;//树的大小
}tree[N];
int root;//根节点
int idx;//此时用的的下标
void pushup(int p){
    tree[p].size = tree[tree[p].l].size + tree[tree[p].r].size + tree[p].cnt;//更新树的大小
}
int new_node(int key){//分配一个节点
    tree[++idx].key = key;
    tree[idx].val = rand();
    tree[idx].cnt = tree[idx].size = 1;
    return idx;
}
//旋转实质是({在满足BST的性质的基础上比较优先级}
//通过交换本节点和其某个叶子节点)把链叉开成二叉形状(从而控制深度)
void zig(int& p){// 右旋
    int q = tree[p].l;
    tree[p].l = tree[q].r, tree[q].r = p, p = q;
    pushup(tree[p].r), pushup(p);
}
void zag(int& p){// 左旋
    int q = tree[p].r;
    tree[p].r = tree[q].l, tree[q].l = p, p = q;
    pushup(tree[p].l), pushup(p);
}
void build(){
    new_node(-INF), new_node(INF);
    root = 1, tree[1].r = 2;
    pushup(root);
    if (tree[1].val < tree[2].val) zag(root);
}
void insert(int& p, int key){
    if (!p) p = new_node(key);//若节点为空,则新建一个节点
    else if (tree[p].key == key) tree[p].cnt++;//若节点已存在,则个数++;
    else if (tree[p].key > key){//比根节点的值小,说明在左子树插入
        insert(tree[p].l, key);
        if (tree[tree[p].l].val > tree[p].val) zig(p);//左子树大的话右旋上去
    }
    else{//比根节点的值大说明在右子树插入
        insert(tree[p].r, key);
        if (tree[tree[p].r].val > tree[p].val) zag(p);//右子树大的话左旋上去
    }
    pushup(p);
}
void remove(int& p, int key){
    if (!p) return;//如果是空树直接返回
    if (tree[p].key == key){//如果当前节点是要删除的节点
        if (tree[p].cnt > 1) tree[p].cnt--;//如果他有多个
        else if (tree[p].l || tree[p].r){//如果他的左儿子存在或者右儿子存在
            if (!tree[p].r || tree[tree[p].l].val > tree[tree[p].r].val){//如果右子树存在或者说左子树的值大于右子树的值
                zig(p);//右旋
                remove(tree[p].r, key);//去右子树删除
            }
            else{
                zag(p);//左旋
                remove(tree[p].l, key);//去左子树删除
            }
        }
        else p = 0;
    }
    //去左右子节点删除
    else if (tree[p].key > key) remove(tree[p].l, key);
    else remove(tree[p].r, key);
    pushup(p);
}
int get_rank_by_key(int p, int key){// 通过数值找排名
    if (!p) return 0;   // 本题中不会发生此情况
    if (tree[p].key == key) return tree[tree[p].l].size + 1;//如果是当前节点返回左子树的大小加一
    if (tree[p].key > key) return get_rank_by_key(tree[p].l, key);//如果当前节点的比寻找的大说明在答案在左子树
    return tree[tree[p].l].size + tree[p].cnt + get_rank_by_key(tree[p].r, key);//在右子树中找,大小要加上左子树的大小和当前根节点的大小
}
int get_key_by_rank(int p, int rank){// 通过排名找数值
    if (!p) return INF;     // 本题中不会发生此情况
    if (tree[tree[p].l].size >= rank) return get_key_by_rank(tree[p].l, rank);//左子树的大小比要找排名大,在左子树中找
    if (tree[tree[p].l].size + tree[p].cnt >= rank) return tree[p].key;//是当前根节点,则返回他的值
    return get_key_by_rank(tree[p].r, rank - tree[tree[p].l].size - tree[p].cnt);//在右子树中找,排名的大小要减去左子树的大小和当前节点的大小
}
int get_prev(int p, int key){   // 找到严格小于key的最大数
    if (!p) return -INF;//为空则返回负无穷
    //包含此数为 > , 不包含为>= 
    if (tree[p].key >= key) return get_prev(tree[p].l, key);//如果当前节点的值比要找的值大,说明在左子树中
    return max(tree[p].key, get_prev(tree[p].r, key));//在满足以上情况下取当前节点和右子树中最大的那个即为答案
}
int get_next(int p, int key){    // 找到严格大于key的最小数
    if (!p) return INF;//如果为空则返回正无穷
    //包含此数为 < , 不好含为<=
    if (tree[p].key <= key) return get_next(tree[p].r, key);//如果大于当前节点的值,说明在右子树中
    return min(tree[p].key, get_next(tree[p].l, key));//在满足以上情况下取当前节点和左子树中最大的那个即为答案
}

int main() {
    ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);

    build();
    int n;
    cin >> n;
    while (n--) {
        int opt, x;
        cin >> opt >> x;
        if (opt == 1) insert(root, x);
        else if (opt == 2) remove(root, x);
        else if (opt == 3) cout << get_rank_by_key(root, x) - 1 << "\n";
        else if (opt == 4) cout << get_key_by_rank(root, x + 1) << "\n";
        else if (opt == 5) cout << get_prev(root, x) << "\n";
        else cout << get_next(root, x) << "\n";
    }

    return 0;
}

AcWing 265. 营业额统计

题目描述

Tiger 最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。

Tiger 拿出了公司的账本,账本上记录了公司成立以来每天的营业额。

分析营业情况是一项相当复杂的工作。

由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。

经济管理学上定义了一种最小波动值来衡量这种情况。

设第 i 天的营业额为 a i a_i ai,则第 i 天 ( i ≥ 2 ) (i≥2) (i2)的最小波动值 f i f_i fi 被定义为:

f i = m i n 1 ≤ j < i ∣ a i − a j ∣ f_i=min_{1≤j<i}|ai−aj| fi=min1j<iaiaj

当最小波动值越大时,就说明营业情况越不稳定。

而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。

你的任务就是编写一个程序帮助 Tiger 来计算这一个值。

第一天的最小波动值为第一天的营业额 a 1 a_1 a1

输入格式

第一行为正整数 n,表示该公司从成立一直到现在的天数。

接下来的 n 行每行有一个整数 a i a_i ai(有可能有负数) ,表示第 i 天公司的营业额。

输出格式

输出一个正整数,表示最小波动值的和。

数据范围

n ≤ 32767 , a i ≤ 1 0 6 n≤32767,a_i≤10^6 n32767,ai106

输入样例:
6
5
1
2
5
4
6
输出样例:
12
样例解释

在样例中, 5 + ∣ 1 − 5 ∣ + ∣ 2 − 1 ∣ + ∣ 5 − 5 ∣ + ∣ 4 − 5 ∣ + ∣ 6 − 5 ∣ = 5 + 4 + 1 + 0 + 1 + 1 = 12 5+|1−5|+|2−1|+|5−5|+|4−5|+|6−5|=5+4+1+0+1+1=12 5+15+21+55+45+65=5+4+1+0+1+1=12

#include<bits/stdc++.h>
#include <unordered_map>
using namespace std;
template<class...Args>
void debug(Args... args) {//Parameter pack
    auto tmp = { (cout << args << ' ', 0)... };
    cout << "\n";
}
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, ll>pll;
typedef pair<int, int>pii;
const ll N = 1e6 + 5;
const ll MOD = 998244353;
const ll INF = 0x7fffffff;

struct Node{
    int l, r;//左右子树
    int key;//值
    int val;//随机分配的值
    int cnt;//有多少个
    int size;//树的大小
}tree[N];
int root;//根节点
int idx;//此时用的的下标
void pushup(int p){
    tree[p].size = tree[tree[p].l].size + tree[tree[p].r].size + tree[p].cnt;//更新树的大小
}
int new_node(int key){//分配一个节点
    tree[++idx].key = key;
    tree[idx].val = rand();
    tree[idx].cnt = tree[idx].size = 1;
    return idx;
}
//旋转实质是({在满足BST的性质的基础上比较优先级}
//通过交换本节点和其某个叶子节点)把链叉开成二叉形状(从而控制深度)
void zig(int& p){// 右旋
    int q = tree[p].l;
    tree[p].l = tree[q].r, tree[q].r = p, p = q;
    pushup(tree[p].r), pushup(p);
}
void zag(int& p){// 左旋
    int q = tree[p].r;
    tree[p].r = tree[q].l, tree[q].l = p, p = q;
    pushup(tree[p].l), pushup(p);
}
void build(){
    new_node(-INF), new_node(INF);
    root = 1, tree[1].r = 2;
    pushup(root);
    if (tree[1].val < tree[2].val) zag(root);
}
void insert(int& p, int key){
    if (!p) p = new_node(key);//若节点为空,则新建一个节点
    else if (tree[p].key == key) tree[p].cnt++;//若节点已存在,则个数++;
    else if (tree[p].key > key){//比根节点的值小,说明在左子树插入
        insert(tree[p].l, key);
        if (tree[tree[p].l].val > tree[p].val) zig(p);//左子树大的话右旋上去
    }
    else{//比根节点的值大说明在右子树插入
        insert(tree[p].r, key);
        if (tree[tree[p].r].val > tree[p].val) zag(p);//右子树大的话左旋上去
    }
    pushup(p);
}
void remove(int& p, int key){
    if (!p) return;//如果是空树直接返回
    if (tree[p].key == key){//如果当前节点是要删除的节点
        if (tree[p].cnt > 1) tree[p].cnt--;//如果他有多个
        else if (tree[p].l || tree[p].r){//如果他的左儿子存在或者右儿子存在
            if (!tree[p].r || tree[tree[p].l].val > tree[tree[p].r].val){//如果右子树存在或者说左子树的值大于右子树的值
                zig(p);//右旋
                remove(tree[p].r, key);//去右子树删除
            }
            else{
                zag(p);//左旋
                remove(tree[p].l, key);//去左子树删除
            }
        }
        else p = 0;
    }
    //去左右子节点删除
    else if (tree[p].key > key) remove(tree[p].l, key);
    else remove(tree[p].r, key);
    pushup(p);
}
int get_rank_by_key(int p, int key){// 通过数值找排名
    if (!p) return 0;   // 本题中不会发生此情况
    if (tree[p].key == key) return tree[tree[p].l].size + 1;//如果是当前节点返回左子树的大小加一
    if (tree[p].key > key) return get_rank_by_key(tree[p].l, key);//如果当前节点的比寻找的大说明在答案在左子树
    return tree[tree[p].l].size + tree[p].cnt + get_rank_by_key(tree[p].r, key);//在右子树中找,大小要加上左子树的大小和当前根节点的大小
}
int get_key_by_rank(int p, int rank){// 通过排名找数值
    if (!p) return INF;     // 本题中不会发生此情况
    if (tree[tree[p].l].size >= rank) return get_key_by_rank(tree[p].l, rank);//左子树的大小比要找排名大,在左子树中找
    if (tree[tree[p].l].size + tree[p].cnt >= rank) return tree[p].key;//是当前根节点,则返回他的值
    return get_key_by_rank(tree[p].r, rank - tree[tree[p].l].size - tree[p].cnt);//在右子树中找,排名的大小要减去左子树的大小和当前节点的大小
}
int get_prev(int p, int key){   // 找到严格小于key的最大数
    if (!p) return -INF;//为空则返回负无穷
    //包含此数为 > , 不包含为>= 
    if (tree[p].key > key) return get_prev(tree[p].l, key);//如果当前节点的值比要找的值大,说明在左子树中
    return max(tree[p].key, get_prev(tree[p].r, key));//在满足以上情况下取当前节点和右子树中最大的那个即为答案
}
int get_next(int p, int key){    // 找到严格大于key的最小数
    if (!p) return INF;//如果为空则返回正无穷
    //包含此数为 < , 不好含为<=
    if (tree[p].key < key) return get_next(tree[p].r, key);//如果大于当前节点的值,说明在右子树中
    return min(tree[p].key, get_next(tree[p].l, key));//在满足以上情况下取当前节点和左子树中最大的那个即为答案
}

int main() {
    ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);

    build();
    int n;
    cin >> n;
    int a1;
    cin >> a1;
    insert(root, a1);
    int ans = a1;
    for (int i = 0; i < n - 1; i++) {
        int t;
        cin >> t;
        if (get_prev(root, t) == -INF) ans += abs(t - get_next(root, t));
        else ans += min(abs(t - get_prev(root, t)), abs(t-get_next(root, t)));
        insert(root, t);
    }
    cout << ans << "\n";
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值