一、斜堆
斜堆是一种可以合并的堆
节点信息:
struct Node {
int v;
Node *ch[];
};
主要利用merge函数
Node *merge(Node *x, Node *y) {
if(!x) return y;
if(!y) return x;
if(x->v < y->v) swap(x, y);
x->ch[] = merge(x->ch[], y);
return swap(x->ch[], x->ch[]), x;
}
左偏树需要维护一个额外的信息,而斜堆每次强制swap(ch[0], ch[1]),以达到均摊$O(\log{n})$的效果
利用merge函数可以很容易地实现插入和删除
void ins(Node*& o, int x) {
o = merge(o, new Node(x));
}
void del(Node*& o) {
o = merge(o->ch[], o->ch[]);
}
另外地,堆相对与平衡树来说无法删除一个元素,但是如果能够定位到这个指针就可以删除这个元素了,方法是存储父亲(merge里要维护一下fa)
删除一个元素时可以这样
void del(Node*& o) {
Node* p = o->fa;
Node* r = merge(o->ch[], o->ch[]);
r->fa = p;
p->ch[p->ch[] == o] = r;
}
当然,和其他树形结构一样,堆上也是可以维护tag的
加入maintain()函数和down()函数后就可以轻松实现这些功能,方法和其他树形结构类似
需要注意的是删除时要像自底向上的splay一样把从这个点到根的路径上的点都down()一遍,再调用del()函数
另外,由于堆高度是均摊$O(\log{n})$的,所以在处理集合信息时不需要额外的并查集,只需要每次暴力往上找到根来比较就能够做到$O(\log{n})$的复杂度了。
!!!
!!!我现在收回 高度并不是均摊$O(\log{n})$高度是可以很大的 是会被卡的!!!!
举一例带标记的题
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string> using namespace std; void setIO(const string& a) {
freopen((a+".in").c_str(), "r", stdin);
freopen((a+".out").c_str(), "w", stdout);
} typedef long long ll;
const int N = + ; struct Node* pis;
struct Node {
ll v, a, m;
int id, fk, tag;
Node* ch[]; void add_tag(ll add, ll mul, int ft) {
if(this) {
fk += ft;
tag += ft;
(v *= mul) += add;
m *= mul;
(a *= mul) += add;
}
} void down() {
ch[]->add_tag(a, m, tag);
ch[]->add_tag(a, m, tag);
tag = a = , m = ;
} void *operator new(size_t) {
return pis++;
} Node(ll v = , int id = ) : v(v), id(id) {
ch[] = ch[] = ;
a = ;
m = ;
fk = tag = ;
}
}pool[N], *root[N]; Node *merge(Node *l, Node *r) {
if(!l || !r) return l ? l : r;
if(l->v > r->v) swap(l, r);
l->down();
l->ch[] = merge(l->ch[], r);
return swap(l->ch[], l->ch[]), l;
} ll h[N], f[N], a[N], v[N];
int ansn[N], ansm[N]; template<typename Q> void gt(Q& x) {
static char c;
static bool f;
for(f = ; c = getchar(), !isdigit(c); ) if(c == '-') f = ;
for(x = ; isdigit(c); c = getchar()) x = x * + c - '';
if(f) x = -x;
} void ddd(Node* o) {
if(!o) return;
o->down();
ddd(o->ch[]);
ddd(o->ch[]);
} int main() {
#ifdef DEBUG
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif pis = pool;
int n, m;
gt(n), gt(m);
for(int i = ; i <= n; i++) {
gt(h[i]);
}
for(int i = ; i <= n; i++) {
gt(f[i]), gt(a[i]), gt(v[i]);
}
for(int i = ; i <= m; i++) {
ll s, c;
gt(s), gt(c);
root[c] = merge(root[c], new Node(s, i));
} for(int i = n; i >= ; i--) {
Node*& r = root[i];
while(r && r->v < h[i]) {
ansn[i]++;
r->down();
r = merge(r->ch[], r->ch[]);
}
ll add = , mul = ;
if(a[i] == ) add += v[i];
else mul *= v[i];
r->add_tag(add, mul, );
root[f[i]] = merge(root[f[i]], r);
} for(int i = ; i <= n; i++) {
printf("%d\n", ansn[i]);
}
ddd(root[]);
for(int i = ; i < m; i++) {
ansm[pool[i].id] = pool[i].fk;
}
for(int i = ; i <= m; i++) {
printf("%d\n", ansm[i]);
} return ;
}
bzoj4003
二、不旋转的treap
参见 http://memphis.is-programmer.com/posts/46317.html
这种treap的实现以分裂(split)和合并(merge)操作为主体,可以解决一些区间问题,比如区间反转。
merge()的过程和斜堆的merge()很像。
其中笛卡尔建树据说是很有用的东西
附tyvj1729文艺平衡树代码,稍作修改就可以实现可持久化
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string> using namespace std; void setIO(const string& a) {
freopen((a+".in").c_str(), "r", stdin);
freopen((a+".out").c_str(), "w", stdout);
} const int N = + ; #define size(x) ((x) ? (x)->sz : 0) struct Node *pis; struct Node {
int fix, v, sz;
bool flip;
Node *l, *r;
Node(int v = ) : v(v) {
fix = rand();
l = r = ;
sz = ;
flip = ;
}
void maintain() {
sz = size(l) + size(r) + ;
}
void down() {
if(flip) {
swap(l, r);
if(l) l->flip ^= ;
if(r) r->flip ^= ;
flip = ;
}
} void* operator new(size_t) {
return pis++;
}
}pool[N], *root;
Node *merge(Node *x, Node *y) {
if(!x) return y;
if(!y) return x;
if(x->fix < y->fix) {
x->down();
x->r = merge(x->r, y);
return x->maintain(), x;
}else {
y->down();
y->l = merge(x, y->l);
return y->maintain(), y;
}
} typedef pair<Node*, Node*> Root; Root split(Node *x, int k) {
if(!x) return Root(, );
Root y; x->down();
if(size(x->l) >= k) {
y = split(x->l, k);
x->l = y.second;
y.second = x;
}else {
y = split(x->r, k - size(x->l) - );
x->r = y.first;
y.first = x;
}
return x->maintain(), y;
} Node *build(int n) {
static Node *stk[N], *x, *last;
int p = ;
for(int i = ; i <= n; i++) {
x = new Node(i);
last = ;
while(p && stk[p]->fix > x->fix) {
stk[p]->maintain();
last = stk[p--];
}
if(p) stk[p]->r = x;
x->l = last;
stk[++p] = x;
}
while(p) stk[p--]->maintain();
return stk[];
} int n; void print(Node *o) {
if(!o) return;
o->down();
print(o->l);
if( < o->v && o->v <= n) printf("%d ", o->v);
print(o->r);
} int main() {
#ifdef DEBUG
freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
#endif pis = pool;
int m;
scanf("%d%d", &n, &m);
root = build(n + );
while(m--) {
int l, r;
scanf("%d%d", &l, &r);
Root x = split(root, l);
Root y = split(x.second, r - l + );
y.first->flip ^= ;
root = merge(merge(x.first, y.first), y.second);
}
print(root); return ;
}
三、替罪羊树
说到不用旋转的平衡树,自然还有替罪羊树
之前好像有一种$O(n\sqrt{n})$的朝鲜树在树高为$O(\sqrt{n})$时重建整棵树
而替罪羊树在是在插入时检查如果某一个子树的左子树或右子树的大小超过该子树的$55%~80%$的时候重建这颗子树
然后就是$O(n\log{n})$的复杂度了...
值得一提的是删除
如果作为树套树的外层是很好处理的
但是如果想实现一棵普通bst的功能,却要复杂一些了。
一种方法是删除时打上删除标记,然后不管(sz信息要变成0),等到下次重建这个点的时候把这个点扔掉
还有一种没有复杂度保证但是很好写的,就是重建改点的左右子树。
附暴力重建子树的代码
tyvj1728普通平衡树
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string> using namespace std; void setIO(const string& a) {
freopen((a+".in").c_str(), "r", stdin);
freopen((a+".out").c_str(), "w", stdout);
} #define szof(x) ((x) ? (x)->sz : 0) const int N = + ; struct Node *pis;
struct Node {
int v, sz;
Node *ch[]; Node(int v = ) : v(v) {
sz = ;
ch[] = ch[] = ;
} void maintain() {
sz = szof(ch[]) + szof(ch[]) + ;
} int cmp(int x) const {
if(x == v) return -;
return x < v ? : ;
} void *operator new(size_t) {
return pis++;
}
}pool[N], *cache[N], *root;
int tot = ; void print(Node *o) {
if(!o) return;
print(o->ch[]);
cache[++tot] = o;
print(o->ch[]);
} void rebuild(Node*& o, int l, int r) {
if(l > r) return o = , void();
int mid = (l + r) >> ;
o = cache[mid];
rebuild(o->ch[], l, mid - );
rebuild(o->ch[], mid + , r);
o->maintain();
} void insert(Node*& o, int x) {
if(!o) o = new Node(x);
else {
int d = o->cmp(x);
if(d == -) d = ;
insert(o->ch[d], x);
}
o->maintain();
} void scape(Node*& o, int x) {
int d = o->cmp(x);
if(d == -) return;
if(o->ch[d]->sz > o->sz * 0.75) {
tot = ;
print(o);
rebuild(o, , tot);
}else scape(o->ch[d], x);
} void insert(int x) {
insert(root, x);
scape(root, x);
} void remove(Node*& o, int x) {
int d = o->cmp(x);
if(d == -) {
if(!o->ch[] && !o->ch[]) o = ;
else {
tot = ;
print(o->ch[]);
print(o->ch[]);
rebuild(o, , tot);
}
}else remove(o->ch[d], x);
if(o) o->maintain();
} int kth(Node *o, int k) {
while(o) {
int s = szof(o->ch[]) + ;
if(s == k) return o->v;
if(s < k) k -= s, o = o->ch[];
else o = o->ch[];
}
return -;
} int rank(Node *o, int x) {
int res = ;
for(int d; o; o = o->ch[d]) {
d = o->cmp(x);
if(d == ) res += szof(o->ch[]) + ;
if(d == -) d = ;
}
return res + ;
} int pre(Node *o, int x) {
int res = -;
for(int d; o; o = o->ch[d]) {
d = o->cmp(x);
if(d == ) res = o->v;
if(d == -) d = ;
}
return res;
} int suf(Node *o, int x) {
int res = -;
for(int d; o; o = o->ch[d]) {
d = o->cmp(x);
if(d == ) res = o->v;
if(d == -) d = ;
}
return res;
} int main() {
#ifdef DEBUG
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif pis = pool;
int n, opt, x;
scanf("%d", &n);
for(int i = ; i <= n; i++) {
scanf("%d%d", &opt, &x);
if(opt == ) insert(x);
else if(opt == ) remove(root, x);
else if(opt == ) printf("%d\n", rank(root, x));
else if(opt == ) printf("%d\n", kth(root, x));
else if(opt == ) printf("%d\n", pre(root, x));
else printf("%d\n", suf(root, x));
} return ;
}
打删除标记的还没想好前驱后继怎么求TAT