【洛谷】P6136 【模板】普通平衡树(数据加强版)

题目地址:

https://www.luogu.com.cn/problem/P6136

题目背景:
本题是P3369数据加强版,扩大数据范围并增加了强制在线。题目的输入、输出和原题略有不同,但需要支持的操作相同。

题目描述:
您需要写一种数据结构(可参考题目标题),来维护一些整数,其中需要提供以下操作:
1、插入一个整数 x x x
2、删除一个整数 x x x(若有多个相同的数,只删除一个)。
3、查询整数 x x x的排名(排名定义为比当前数小的数的个数+1)。
4、查询排名为 x x x的数(如果不存在,则认为是排名小于 x x x的最大数。保证 x x x不会超过当前数据结构中数的总数)。
5、求 x x x的前驱(前驱定义为小于 x x x,且最大的数)。
6、求 x x x的后继(后继定义为大于 x x x,且最小的数)。
本题强制在线,保证所有操作合法(操作 2 2 2保证存在至少一个 x x x,操作 4 , 5 , 6 4,5,6 4,5,6保证存在答案)。

输入格式:
第一行两个正整数 n , m n,m n,m,表示初始数的个数和操作的个数。
第二行 n n n个整数 a 1 , a 2 , a 3 , … , a n a_1,a_2,a_3,\ldots,a_n a1,a2,a3,,an,表示初始的数。接下来 m m m行,每行有两个整数 opt \text{opt} opt x ′ x' x opt \text{opt} opt表示操作的序号( 1 ≤ opt ≤ 6 1 \leq \text{opt} \leq 6 1opt6), x ′ x' x表示加密后的操作数。我们记 last \text{last} last表示上一次 3 , 4 , 5 , 6 3,4,5,6 3,4,5,6操作的答案,则每次操作的 x ′ x' x都要异或上 last \text{last} last才是真实的 x x x。初始 last \text{last} last 0 0 0

输出格式:
输出一行一个整数,表示所有 3 , 4 , 5 , 6 3,4,5,6 3,4,5,6操作的答案的异或和。

数据范围:
对于 100 % 100\% 100%的数据, 1 ≤ n ≤ 1 0 5 1\leq n\leq 10^5 1n105 1 ≤ m ≤ 1 0 6 1\leq m\leq 10^6 1m106 0 ≤ a i , x < 2 30 0\leq a_i,x\lt 2^{30} 0ai,x<230

可以用FHQ Treap来做,参考https://blog.csdn.net/qq_46105170/article/details/118997891。下面的求排名 r r r的数的操作与参考链接有所不同,这个操作可以采用普通平衡树的办法迭代或递归地做,也可以考虑按size来分裂的操作,此操作定义如下:
设当前树里数字总个数为 n n n(这里的 n n n并不是节点的个数,因为每个节点里会记录一个cnt代表这个数字出现了多少次。当然也可以将出现多次的值存多个节点里),考虑 k k k是一个 1 ∼ n 1\sim n 1n的数,按 k k k分裂的含义是,将树分裂为两棵树 x x x y y y,其中 x x x的数字个数是大于等于 k k k的最小值。
那么求排名为 r r r的数可以这样做,先按 r r r分裂,然后求小树的最大值即可。

代码如下:

#include <iostream>
using namespace std;

const int N = 2e6 + 10;
int n, m;
struct Node {
    int l, r;
    int v, rnd;
    int sz, cnt;
} tr[N];
int root, idx;
int x, y, z;
int res, last;

int get_node(int v) {
    tr[++idx].v = v;
    tr[idx].l = tr[idx].r = 0;
    tr[idx].sz = tr[idx].cnt = 1;
    tr[idx].rnd = rand();
    return idx;
}

void pushup(int u) {
    tr[u].sz = tr[tr[u].l].sz + tr[tr[u].r].sz + tr[u].cnt;
}

// 按key分裂,分裂为小于等于key和大于key的两棵树
void split(int u, int key, int &x, int &y) {
    if (!u) x = y = 0;
    else {
        if (tr[u].v <= key) {
            x = u;
            split(tr[u].r, key, tr[u].r, y);
            pushup(x);
        } else {
            y = u;
            split(tr[u].l, key, x, tr[u].l);
            pushup(y);
        }
    }
}

// 按sz分裂,分裂为两棵树,小树的size是大于等于sz的最小的数
void split_sz(int u, int sz, int &x, int &y) {
    if (!u) x = y = 0;
    else {
        if (sz > tr[tr[u].l].sz) {
            x = u;
            split_sz(tr[u].r, sz - tr[tr[u].l].sz - tr[u].cnt, tr[u].r, y);
            pushup(x);
        } else {
            y = u;
            split_sz(tr[u].l, sz, x, tr[u].l);
            pushup(y);
        }
    }
}

int merge(int x, int y) {
    if (!x || !y) return x | y;
    if (tr[x].rnd > tr[y].rnd) {
        tr[x].r = merge(tr[x].r, y);
        pushup(x);
        return x;
    } else {
        tr[y].l = merge(x, tr[y].l);
        pushup(y);
        return y;
    }
}

int merge(int x, int y, int z) {
    return merge(merge(x, y), z);
}

void insert(int c) {
    split(root, c, y, z);
    split(y, c - 1, x, y);
    if (!y) y = get_node(c);
    else {
        tr[y].cnt++;
        tr[y].sz++;
    }

    root = merge(x, y, z);
}

void remove(int c) {
    split(root, c, y, z);
    split(y, c - 1, x, y);
    tr[y].cnt--;
    tr[y].sz--;
    if (!tr[y].cnt) y = 0;
    root = merge(x, y, z);
}

int get_rank_by_key(int c) {
    split(root, c - 1, x, y);
    int rk = tr[x].sz + 1;
    root = merge(x, y);
    return rk;
}

int get_key_by_rank(int rk) {
    split_sz(root, rk, x, y);
    int u = x, key;
    while (u) key = tr[u].v, u = tr[u].r;
    root = merge(x, y);
    return key;
}

int get_prev(int c) {
    split(root, c - 1, x, y);
    int u = x, key;
    while (u) key = tr[u].v, u = tr[u].r;
    root = merge(x, y);
    return key;
}

int get_next(int c) {
    split(root, c, x, y);
    int u = y, key;
    while (u) key = tr[u].v, u = tr[u].l;
    root = merge(x, y);
    return key;
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        int c;
        scanf("%d", &c);
        insert(c);
    }

    while (m--) {
        int op, c;
        scanf("%d%d", &op, &c);
        c ^= last;
        
        if (op == 1) insert(c);
        else if (op == 2) remove(c);
        else {
            int w;
            if (op == 3) w = get_rank_by_key(c);
            else if (op == 4) w = get_key_by_rank(c);
            else if (op == 5) w = get_prev(c);
            else w = get_next(c);

            res ^= w;
            last = w;
        }
    }

    printf("%d\n", res);

    return 0;
}

每次操作时间复杂度 O ( log ⁡ n ) O(\log n) O(logn) n n n指的是操作的时候树里有多少个节点),空间 O ( n ) O(n) O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值