leafy tree 結構, 大概是 k 叉樹的非葉節點都有 k 個子節點, 比如線段樹就是 leafy 的。
可以用 leafy tree 結構實現加權平衡樹, 大概是叫做 WBLT(Weight Balanced Leafy Tree)。這個 WBLT 要維護的原始信息全都存儲在葉節點上,對於每個插入進 WBLT 的原始信息 ai,每個葉節點都有 value 和 size 兩種鍵值, 對於 ai 對應的葉節點, 有 value = ai, size = 1; 對於非葉節點, 其 value 等於其右子節點的 value, 其 size 等於其左子節點和其右子節點的 size 之和。(其實非葉節點的 value 還可定義為左子節點的 value, 不同的定義會造成具體操作的實現不同,以下都默認是右子節點的 value)
對於 WBLT 中序遍歷形成的序列中葉子節點的相對順序,要保證是從小到大排序后的相對順序,以下暫且稱其為 WBLT 性質。這樣, 一個子樹的根節點的 value 就是其子樹中 value 最大的葉節點的 value,查找什么的操作就都容易寫了。
一般來說用指針寫比較清爽,具體來說就是 me->ls->ls->size
和 t[t[t[me].ls].ls].siz
的區別。對於指針回收問題, 一般寫個內存池。
節點這么寫:
struct node{
int siz, val;
node *ls, *rs;
node( int s, int v, node *a, node *b) : siz(s),val(v),ls(a),rs(b) {}
node () {}
} *root, *null, t[100], *pool[100];
int cnt = 0; // pool 用
int main()
{
null = new node(0, 0, NULL, NULL); // 因為直接訪問空指針會報錯
root = new node(1, INF, null, null); // 初始時樹為哨兵節點
for(int i=0; i<100; ++i) pool[i] = &t[i]; // 內存池初始化
return 0;
}
這樣, 創建新節點的操作就可以寫成:
#define newnode(s, v, a, b) (&(*pool[cnt++] = node(s, v, a, b)))
簡潔!賦值的同時還返回了地址 ovo。
回收指針的時候直接 pool[--cnt] = /*pointer name*/
就行了。
插入與刪除操作:
不難發現由於 WBLT 非葉節點的 value 的性質, 很容易找到插入與刪除的位置, 此時插入與刪除的區別就是新建節點與刪除節點的區別了。 不難發現對於每次插入操作, 都要新建兩個節點,故 WBLT 比起常見的那種所有節點都存儲原始信息的平衡樹, 要消耗兩倍的空間。
旋轉平衡:
這里只介紹單旋, 聽人說沒人卡……我猜單旋是可以卡的, 其復雜度證明我沒見過, 但雙旋的復雜度是有證明的。對於一個節點,其左兒子與右兒子的 size 差距過大時, 要旋轉。具體來說:
#define ratio 4
// 我看其他人的寫法都是定義的 4...
#define merge(a, b) newnode(a->siz+b->siz, b->val, a, b)
// 即對於特定的左子節點和右子節點生成一個父親
inline void maintain(register node * me)
{
if(me->ls->siz > me->rs->siz * ratio)
me->rs = merge(me->ls->rs, me->rs), st[--cnt] = me->ls, me->ls =me->ls->ls;
if(me->rs->siz > me->ls->siz * ratio)
me->ls = merge(me->ls, me->rs->ls), st[--cnt] = me->rs, me->rs =me->rs->rs;
}
即, 直接把過重的兒子的一部分拿到另一個兒子上, 然而我還是不會證復雜度……
這個旋轉操作挺有啟發性的, 對於那些 treap,splay 一類的平衡樹的單旋操作, 也可以看成是像這樣的 “重量讓渡”。
忘了說了, 很容易證明旋轉后整棵樹還是滿足 WBLT 性質的。
又忘了說了,在葉子節點及葉子節點的父親節點處旋轉會錯誤, 但是由於 if 語句的存在, 在這兩處不會旋轉。
貼個普通平衡樹的代碼吧:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int N=1e5+233;
#define newnode(s, v, a, b) (&(*pool[cnt++] = node(s, v, a, b)))
#define merge(a, b) newnode(a->siz+b->siz, b->val, a, b)
#define upd(me) if(me->ls->siz) me->siz=me->ls->siz+me->rs->siz, me->val=me->rs->val
#define ratio 4
struct node{
int siz,val;
node *ls, *rs;
node(int s, int v, node *a, node *b) : siz(s), val(v), ls(a), rs(b) {}
node() {}
} *root, *null, t[N<<1], *pool[N<<1];
int n, cnt;
inline void maintain(register node *me) {
if(me->ls->siz > me->rs->siz*ratio)
me->rs=merge(me->ls->rs, me->rs), pool[--cnt]=me->ls, me->ls=me->ls->ls;
if(me->rs->siz > me->ls->siz*ratio)
me->ls=merge(me->ls, me->rs->ls), pool[--cnt]=me->rs, me->rs=me->rs->rs;
}
void ins(int x,node *me) {
if(me->siz == 1) me->ls = newnode(1, min(x,me->val), null, null), me->rs = newnode(1, max(x,me->val), null, null);
else ins(x, x>me->ls->val ? me->rs : me->ls);
upd(me); maintain(me);
}
void era(int x,node *me) {
if(me->ls->siz==1 && me->ls->val==x)
pool[--cnt]=me->ls, pool[--cnt]=me->rs, *me=*me->rs;
else if(me->rs->siz==1 && me->rs->val==x)
pool[--cnt]=me->rs, pool[--cnt]=me->ls, *me=*me->ls;
else era(x, x>me->ls->val ? me->rs : me->ls);
upd(me); maintain(me);
}
int fid(int x,node *me) {
if(me->siz == 1) return me->val;
return x>me->ls->siz ? fid(x-me->ls->siz, me->rs) : fid(x, me->ls);
}
int rnk(int x,node *me) {
if(me->siz == 1) return 1;
return x>me->ls->val ? me->ls->siz + rnk(x, me->rs) : rnk(x, me->ls);
}
int main()
{
null = new node(0, 0, NULL, NULL);
root = new node(1,INF,null,null);
for(int i=0;i<(N<<1);++i) pool[i]=&t[i];
scanf("%d",&n);
int opt,x;
while(n--)
{
scanf("%d%d",&opt,&x);
if(opt==1) ins(x, root);
else if(opt==2) era(x, root);
else if(opt==3) printf("%d\n", rnk(x, root));
else if(opt==4) printf("%d\n", fid(x, root));
else if(opt==5) printf("%d\n", fid(rnk(x, root)-1, root));
else printf("%d\n", fid(rnk(x+1, root), root));
}
return 0;
}