题目
Description
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
- 插入x数
- 删除x数(若有多个相同的数,因只删除一个)
- 查询x数的排名(若有多个相同的数,因输出最小的排名)
- 查询排名为x的数
- 求x的前驱(前驱定义为小于x,且最大的数)
- 求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
n
的数据范围:n<=100000
- 每个数的数据范围:
[-2e9,2e9]
题解
这道题是平衡树的模板题,是平衡树都能用,我用了三种方法写
BST二叉排序树或平衡树具有以下性质:
- 若左子树不为空,则左子树所有结点的值都小于等于它根结点的值
- 若右子树不为空,则右子树所有结点的值都大于等于它根结点的值
- 左右子树也分别为二叉排序树
伸展树 Splay
可以通过旋转来更换结点位置
插入 insert
从根结点开始,如果比x
大则向左,如果比x
小就向右,如果相等则增加其count
的值,没有就新建一个结点
删除 erase
从根结点开始,找到该点,splay
到根结点,若count
大于1
则自减,其余情况:
- 没有子结点,删除根结点,令
root=nullptr
- 有左或右儿子,令
root
为该儿子,删除该结点 - 两个儿子都有,将右子树最小结点
splay
到根的右儿子,令根的左儿子成为右儿子的左儿子,删除该结点
查询x的排名 rank
从根结点开始,找到该点,splay
到根结点,返回左子树size
加上1
查询排名为x的数 search
从根结点开始,若左儿子size
大于等于x
,向左走,若加上该结点size
小于x
向右走,若为该结点则返回
前驱 predeccessor
插入x
,splay
到根结点,返回左子树最大值,删除x
后继 successor
插入x
,splay
到根结点,返回右子树最小值,删除x
完整代码 code
#include<cstdio>
using namespace std;
int f(-1);
inline char GetCharacter() {
static char buf[2000000], *p1 = buf, *p2 = buf;
return (p1 == p2) &&
(p2 = (p1 = buf) + fread(buf, 1, 2000000, stdin), p1 == p2) ?
EOF : *p1++;
}
#define IS_DIGIT(c) (c >= '0' && c <= '9')
inline void Read(int &x) {
f = 1, x = 0;
static char c = GetCharacter();
while (!IS_DIGIT(c)) {
if (c == '-') f = -1;
c = GetCharacter();
}
while (IS_DIGIT(c)) x = x * 10 + c - '0', c = GetCharacter();
x *= f;
}
#undef IS_DIGIT
class splay_tree {
private:
struct node {
int value, size, count;
node *left, *right, *father;
node(const int v = 0) {
value = v, size = 1, count = 1;
left = right = father = NULL;
}
}*root;
inline void update(register node *x) {
if (x) x->size = x->count +
(x->left ? x->left->size : 0) +
(x->right ? x->right->size : 0);
}
inline void left_rotate(register node *x) {
register node *y = x->father;
y->right = x->left;
if (x->left) x->left->father = y;
x->father = y->father;
if (y->father) {
if (y == y->father->left) y->father->left = x;
else y->father->right = x;
}
x->left = y;
y->father = x;
update(y);
update(x);
}
inline void right_rotate(register node *x) {
register node *y = x->father;
y->left = x->right;
if (x->right) x->right->father = y;
x->father = y->father;
if (y->father) {
if (y == y->father->right) y->father->right = x;
else y->father->left = x;
}
x->right = y;
y->father = x;
update(y);
update(x);
}
inline void splay(register node *x,const node *target=NULL) {
register node *y;
while (x->father != target) {
y = x->father;
if (x == y->left) {
if (y->father != target && y == y->father->left) right_rotate(y);
right_rotate(x);
} else {
if (y->father != target && y == y->father->right) left_rotate(y);
left_rotate(x);
}
}
if (!target) root = x;
}
inline node *find(const int &key) {
register node *x = root;
while (1) {
if (!x) break;
else if (x->value == key)break;
if (x->value < key) x = x->right;
else if (x->value > key) x = x->left;
}
if (x) splay(x);
return x;
}
inline node *subtree_maximum(register node *x) {
if (!x) return NULL;
while (x->right) x = x->right;
return x;
}
inline node *subtree_minimum(register node *x) {
if (!x) return NULL;
while (x->left) x = x->left;
return x;
}
public:
inline void insert(const int &key) {
register node *x = root, *y = NULL;
while (1) {
if (!x) break;
else if (x->value == key) break;
y = x;
if (x->value < key) x = x->right;
else if (x->value > key) x = x->left;
}
if (x) ++x->count, ++x->size;
else {
x = new node(key);
x->father = y;
if (y) {
if (x->value > y->value) y->right = x;
else y->left = x;
}
}
if (!y) root = x;
update(y);
splay(x);
}
inline void erase(const int &key) {
register node *x = find(key);
if (x) {
if (x->count > 1) --x->count, --x->size;
else {
if (!x->left && !x->right) {
delete root;
root = NULL;
return;
} else if (!x->left) {
root = x->right;
root->father = NULL;
delete x;
return;
} else if (!x->right) {
root = x->left;
root->father = NULL;
delete x;
return;
} else {
register node *y = subtree_minimum(x->right);
splay(y, x);
y->left = x->left, x->left->father = y;
y->father = NULL;
delete x;
root = y;
update(y);
}
}
}
}
inline int rank(const int &key) {
register node *x = find(key);
if (x) return (((x->left ? x->left->size : 0 ) + 1));
else return 0;
}
inline int search(register int rnk) {
register node *x = root;
while(1) {
if (x->left) {
if (x->left->size < rnk && x->left->size + x->count >= rnk) break;
else if (x->left->size >= rnk) x = x->left;
else rnk -= x->left->size + x->count, x = x->right;
} else {
if (x->count >= rnk) break;
else rnk -= x->count, x = x->right;
}
}
return x->value;
}
inline int predeccessor(const int &key) {
insert(key);
register node *ret = subtree_maximum(root->left);
splay(ret);
erase(key);
return ret->value;
}
inline int successor(const int &key) {
insert(key);
register node *ret = subtree_minimum(root->right);
splay(ret);
erase(key);
return ret->value;
}
};
int n, opt, x;
splay_tree tree;
int main(int argc, char **argv) {
scanf("%d", &n);
while (n--) {
scanf("%d %d", &opt, &x);
switch(opt) {
case 1: {
tree.insert(x);
break;
}
case 2: {
tree.erase(x);
break;
}
case 3: {
printf("%d
", tree.rank(x));
break;
}
case 4: {
printf("%d
", tree.search(x));
break;
}
case 5: {
printf("%d
", tree.predeccessor(x));
break;
}
case 6: {
printf("%d
", tree.successor(x));
break;
}
}
}
return 0;
}
非旋树堆 FHQ Treap
只有两个操作,能够实现可持久化
分离 split
按键值将tree
分为x
,y
两棵树(也可按权值)
合并 merge
按权值将两棵树合并为一棵树
插入 insert
将原树分为x
,y
两棵树,将新节点看成一棵树与x
合并,再与y
合并
删除 erase
首先我们把树分为x
和z
两部分
那么x
树中的最大权值为a
再把x
分为x
和y
两部分。
此时x
中的最大权值为a-1
,且权值为a
的节点一定是y
的根节点。
然后我们可以无视y
的根节点,直接把y
的左右孩子合并起来,这样就成功的删除了根节点,
最后再把x
,y
,z
合并起来就好
其他操作见代码
完整代码 code
#include<cstdio>
#include<cstdlib>
using namespace std;
int f(-1);
inline char GetCharacter() {
static char buf[2000000], *p1 = buf, *p2 = buf;
return (p1 == p2) &&
(p2 = (p1 = buf) + fread(buf, 1, 2000000, stdin), p1 == p2) ?
EOF : *p1++;
}
#define IS_DIGIT(c) (c >= '0' && c <= '9')
inline void Read(int &x) {
f = 1, x = 0;
static char c = GetCharacter();
while (!IS_DIGIT(c)) {
if (c == '-') f = -1;
c = GetCharacter();
}
while (IS_DIGIT(c)) x = x * 10 + c - '0', c = GetCharacter();
x *= f;
}
#undef IS_DIGIT
class fhq_treap {
private:
struct node {
node *left, *right;
int value, prize, size;
node(const int v = 0) {
left = right = NULL;
value = v, prize = rand(), size = 1;
}
}*root, *x, *y, *z;
int size;
inline void update(register node *x) {
x->size = 1 + (x->left ? x->left->size : 0) +
(x->right ? x->right->size : 0);
}
inline node *new_node(const int v = 0) {
++size;
register node *tmp = new node(v);
return tmp;
}
inline node *merge(register node *x, register node *y) {
if (!x) return y;
if (!y) return x;
if (x->prize < y->prize) {
x->right = merge(x->right, y);
update(x);
return x;
} else {
y->left = merge(x, y->left);
update(y);
return y;
}
}
inline void split(register node *now,const int k,register node *&x,
register node *&y) {
if (!now) x = y = NULL;
else {
if (now->value <= k) x = now, split(now->right, k, now->right, y);
else y = now, split(now->left, k, x, now->left);
update(now);
}
}
inline node *kth(register node *now, register int k) {
while(1) {
if (now->left != NULL) {
if (k <= now->left->size) now = now -> left;
else if (k == (now->left ? now->left->size : 0) + 1) return now;
else k -= (now->left ? now->left->size : 0) + 1, now = now->right;
} else if (k == (now->left ? now->left->size : 0) + 1) return now;
else k -= (now->left ? now->left->size : 0) + 1, now = now->right;
}
}
public:
fhq_treap() {
size = 0;
root = NULL;
}
inline void insert(const int &key) {
split(root, key, x, y);
root = merge(merge(x, new_node(key)), y);
}
inline void erase(const int &key) {
split(root, key, x, z);
split(x, key - 1, x, y);
y = merge(y->left, y->right);
root = merge(merge(x, y), z);
}
inline int rank(const int key) {
split(root, key - 1, x, y);
register int ret = (x ? x->size : 0) + 1;
root = merge(x, y);
return ret;
}
inline int search(const int &rnk) {
return kth(root, rnk)->value;
}
inline int predeccessor(const int &key) {
split(root, key - 1, x, y);
register int ret(kth(x, x->size)->value);
root = merge(x, y);
return ret;
}
inline int successor(const int &key) {
split(root, key, x, y);
register int ret(kth(y, 1)->value);
root = merge(x, y);
return ret;
}
};
int n, opt, x;
fhq_treap tree;
int main(int argc, char **argv) {
Read(n);
while(n--) {
scanf("%d %d", &opt, &x);
Read(opt), Read(x);
switch(opt) {
case 1: {
tree.insert(x);
break;
}
case 2: {
tree.erase(x);
break;
}
case 3: {
printf("%d
", tree.rank(x));
break;
}
case 4: {
printf("%d
", tree.search(x));
break;
}
case 5: {
printf("%d
", tree.predeccessor(x));
break;
}
case 6: {
printf("%d
", tree.successor(x));
break;
}
}
}
return 0;
}
宗法树 Patriarchal Tree
这是一个非常毒瘤的数据结构,因其删除操作特点而得名
它的特点有:
- 叶节点存的是数,非叶节点存的是左右两棵子树的最大值
- 非叶节点左子树的值都比右子树的值小
- 非叶节点必有左右两棵子树
插入 insert
若无结点则新建,若有,根据性质找到一个叶结点,新建两个子结点,按顺序排这两个数,更新原结点
删除 erase
找到该点,用另一子结点将父亲覆盖
其他操作见代码
完整代码 code
洛谷最新数据会被卡掉一个点
#include<cstdio>
#include<algorithm>
using namespace std;
int f(-1);
inline char GetCharacter() {
static char buf[2000000], *p1 = buf, *p2 = buf;
return (p1 == p2) &&
(p2 = (p1 = buf) + fread(buf, 1, 2000000, stdin), p1 == p2) ?
EOF : *p1++;
}
#define IS_DIGIT(c) (c >= '0' && c <= '9')
inline void Read(int &x) {
f = 1, x = 0;
static char c = GetCharacter();
while (!IS_DIGIT(c)) {
if (c == '-') f = -1;
c = GetCharacter();
}
while (IS_DIGIT(c)) x = x * 10 + c - '0', c = GetCharacter();
x *= f;
}
#undef IS_DIGIT
class patriarchal_tree {
private:
struct node {
int val, size;
node *left, *right, *father;
node(const int v = 0) {
val = v, size = 1, left = right = father = NULL;
}
}*root;
inline void update(register node *x){
if (x && x->left) x->val = max(x->left->val, x->right->val),
x->size = x->left->size + x->right->size;
}
inline bool isleaf(const node *x){
return x->left == NULL;
}
inline void Insert(const int &v,register node *s){
if (!s) {
s = new node(v);
root = s;
return;
}else if (!s->left) {
s->left = new node (min(s->val , v));
s->right = new node(max(s->val , v));
s->left->father = s->right->father = s;
update(s);
return;
}
if (v > s->left->val) {
Insert(v, s->right);
} else Insert(v, s->left);
update(s);
}
inline void Erase(const int &v,register node *s){
if (isleaf(s)) {
register node *p = s->father, *t;
if (!p) {
root = NULL;
delete s;
}
else if (p->right == s) {
t = p->left;
if (p->left->left) {
p->left->left->father = p->left->right->father = p;
}
p->val = p->left->val, p->size = p->left->size;
p->left = t->left, p->right = t->right;
delete t;
delete s;
}
else {
t = p->right;
if (p->right->left) {
p->right->left->father = p->right->right->father = p;
}
p->val = p->right->val, p->size = p->right->size;
p->right = t->right, p->left = t->left;
delete t;
delete s;
}
return;
}
if (v > s->left->val) {
Erase(v, s->right);
}else Erase(v, s->left);
update(s);
}
inline int Rank(const int &v,const node *s){
if (isleaf(s)) {
if (s->val < v) return 2;
return 1;
}
if(v > s->left->val) return Rank(v, s->right) + s->left->size;
else return Rank(v, s->left);
}
inline int Search(register int v,const node *s){
if (isleaf(s)) return s->val;
else if (v > s->left->size) return Search(v - s->left->size, s->right);
else return Search(v, s->left);
}
public:
patriarchal_tree(){
root = NULL;
}
inline void insert(const int &v){
Insert(v, root);
}
inline void erase(const int &v){
Erase(v, root);
}
inline int rank(const int &v){
return Rank(v, root);
}
inline int search(const int &v){
return Search(v, root);
}
inline int predecessor(const int &v){
return Search(Rank(v, root) - 1, root);
}
inline int successor(const int &v){
return Search(Rank(v + 1, root), root);
}
};
patriarchal_tree T;
int n, opt, x;
int main(int argc, char **argv){
Read(n);
while (n--) {
Read(opt), Read(x);
if (opt == 1) T.insert(x);
else if (opt == 2) T.erase(x);
else if (opt == 3) printf("%d
", T.rank(x));
else if (opt == 4) printf("%d
", T.search(x));
else if (opt == 5) printf("%d
", T.predecessor(x));
else if (opt == 6) printf("%d
", T.successor(x));
}
return 0;
}
2018.7.24更新
vector解法
突然想起来可以用vector
过,虽然非常暴力但真的过了
代码 code
比指针版的非旋Treap还快(可能是我写的有毛病)
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
int f(-1);
inline char GetCharacter() {
static char buf[2000000], *p1 = buf, *p2 = buf;
return (p1 == p2) &&
(p2 = (p1 = buf) + fread(buf, 1, 2000000, stdin), p1 == p2) ?
EOF : *p1++;
}
#define IS_DIGIT(c) (c >= '0' && c <= '9')
inline void Read(int &x) {
f = 1, x = 0;
static char c = GetCharacter();
while (!IS_DIGIT(c)) {
if (c == '-') f = -1;
c = GetCharacter();
}
while (IS_DIGIT(c)) x = x * 10 + c - '0', c = GetCharacter();
x *= f;
}
#undef IS_DIGIT
vector<int> v;
int n, opt, x;
int main(int argc, char **argv) {
scanf("%d", &n);
v.reserve(n + 5);
while (n--) {
scanf("%d %d", &opt, &x);
switch(opt) {
case 1:
v.insert(upper_bound(v.begin(), v.end(), x), x);
break;
case 2:
v.erase(lower_bound(v.begin(), v.end(), x));
break;
case 3:
printf("%d
", lower_bound(v.begin(), v.end(), x) - v.begin() + 1);
break;
case 4:
printf("%d
", v[x - 1]);
break;
case 5:
printf("%d
", *--lower_bound(v.begin(), v.end(), x));
break;
case 6:
printf("%d
", *upper_bound(v.begin(), v.end(), x));
}
}
return 0;
}
2018.8.17更新
树状数组 fenwick tree
又一个令马制烯的方法,但对于平衡树的题树状数组估计也就只能干这个了,不过跑得飞快
离散化 Discretization
为了节省内存,我们在这里进行离散化,当然如果数据范围过大则必须进行离散化
当然离散化后就只能离线做了
离散化后排序去重,为了方便可以使用STL
修改 Add
树状数组基本操作,不再解释
求和 GetSum
树状数组基本操作,不再解释
插入 Insert
将x
离散化后的数作为下标,在树状数组对应的地方增加1
删除 Erase
将x
离散化后的数作为下标,在树状数组对应的地方减去1
查询排名 Rank
求当前树状数组中下标小于DISCRETE(x)
的前缀和
查询排名为x的数 Search
这里是树状数组解法中最困难的一个,用到了倍增的思想
当我们把fenwick_tree[i]
的下标加上1 << k
后,
fenwick_tree[i + (1 << k)]
的值就是我们增加的这一段区间元素的个数
就可以直接判断当前元素个数是否超过了rank
,超过了就退回去,否则就保存
求x的前驱 Predecessor
找到比该数排名小1
的数
求x得后继 Successor
与求前驱同理
完整代码 Code
#include<cstdio>
#include<algorithm>
using namespace std;
int f(-1);
inline char GetCharacter() {
static char buf[2000000], *p1 = buf, *p2 = buf;
return (p1 == p2) &&
(p2 = (p1 = buf) + fread(buf, 1, 2000000, stdin), p1 == p2) ?
EOF : *p1++;
}
#define IS_DIGIT(c) (c >= '0' && c <= '9')
inline void Read(int &x) {
f = 1, x = 0;
static char c = GetCharacter();
while (!IS_DIGIT(c)) {
if (c == '-') f = -1;
c = GetCharacter();
}
while (IS_DIGIT(c)) x = x * 10 + c - '0', c = GetCharacter();
x *= f;
}
#undef IS_DIGIT
#define LOWBIT(i) ((i) & (-i))
int fenwick_tree[100010], tree_size;
int table[100010], number_quantity;
inline void Add(register int index, const int &delta) {
while (index <= tree_size) {
fenwick_tree[index] += delta;
index += LOWBIT(index);
}
}
inline int GetSum(register int index) {
register int ret(0);
while (index) {
ret += fenwick_tree[index];
index -= LOWBIT(index);
}
return ret;
}
inline void Discretization() {
sort(table + 1, table + number_quantity + 1);
tree_size = unique(table + 1, table + number_quantity + 1) - (table + 1);
}
#define DISCRETE(x) lower_bound(table + 1, table + tree_size + 1, x) - table
inline void Insert(const int &key) {
Add(DISCRETE(key), 1);
}
inline void Erase(const int &key) {
Add(DISCRETE(key), -1);
}
inline int Rank(const int &key) {
return GetSum(DISCRETE(key) - 1) + 1;
}
inline int Search(register int rank) {
register int ret(0);
for (register int i(20); i >= 0; --i) {
ret += 1 << i;
if(ret >= tree_size || fenwick_tree[ret] >= rank) ret -= 1 << i;
else rank -= fenwick_tree[ret];
}
return table[++ret];
}
inline int Predeccessor(const int &key) {
return Search(GetSum(DISCRETE(key) - 1));
}
inline int Successor(const int &key) {
return Search(GetSum(DISCRETE(key)) + 1);
}
#undef LOWBIT
int n, opt[100010], x[100010];
int main(int argc, char **argv) {
Read(n);
for (register int i(1); i <= n; ++i) {
Read(opt[i]), Read(x[i]);
if (opt[i] != 4) table[++number_quantity] = x[i];
}
Discretization();
for (register int i(1); i <= n; ++i) {
switch(opt[i]) {
case 1: {
Insert(x[i]);
break;
}
case 2: {
Erase(x[i]);
break;
}
case 3: {
printf("%d
", Rank(x[i]));
break;
}
case 4: {
printf("%d
", Search(x[i]));
break;
}
case 5: {
printf("%d
", Predeccessor(x[i]));
break;
}
case 6: {
printf("%d
", Successor(x[i]));
break;
}
}
}
return 0;
}