题目地址:
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 1≤opt≤6), 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 1≤n≤105, 1 ≤ m ≤ 1 0 6 1\leq m\leq 10^6 1≤m≤106, 0 ≤ a i , x < 2 30 0\leq a_i,x\lt 2^{30} 0≤ai,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 1∼n的数,按 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)。