学习笔记:左偏树
左偏树是一种能够在支持在 $O(logn)$ 的时间复杂度内进行合并的满足堆性质的树,因此别名“可并堆”,具有可合并与复杂度稳定的优势。
基本概念与定义
- 外结点:至少有一个儿子是空节点的节点。
- $dist_x$ 表示以 $x$ 为根的子树内到 $x$ 距离最小的外结点的距离。特别的,对于空结点,其 $dist=-1$
左偏树的性质
-
左偏树满足小根堆性质(大根堆貌似也可以),即对于结点 $x$,总有 $v_x\leq v_{lc}$ 且 $v_x\leq v_{rc}$。
- 左偏树满足左偏的性质,即对于任意节点均有:$dist_{lc}\geq dist_{rc}$。
显然的结论
- $dist_x=dist_{rc}+1$。
- 一棵有 $n$ 个结点的左偏树其根节点的 $dist$ 是 $O(logn)$ 的。(可以保证合并的复杂度的稳定性)
合并操作
显然我们对于两个堆我们会一个一个比较合并,复杂度与树高正相关。显然,如果退化成链,复杂度将为 $O(n)$。这时我们左偏树的优势就体现出来了,由上面的结论 2,一次合并的复杂度稳定为 $O(logn)$。由左偏的性质,每次合并时,我们选择 $dist$ 的更小的右儿子去合并,这个过程可以递归实现,在过程中利用 $swap$ 维护堆性质,并在合并后回溯时维护左偏的性质即可。
1 int merge(int x,int y) 2 { 3 if(not x or not y) return x + y; 4 if(val[y] < val[x]) swap(x,y); 5 rc[x] = merge(rc[x],y); 6 if(dist[lc[x]] < dist[rc[x]]) swap(lc[x],rc[x]); 7 dist[x] = dist[rc[x]] + 1; 8 return x; 9 }
删除所在堆顶元素操作
这里我们需要对于每个结点维护一个 $root_x$ 表示该节点所在的堆顶元素(即所在的左偏树根),那么对于 $root$ 的维护,如果合并后暴力的一个一个跳,复杂度将退化为 $O(n)$。我们联想到并查集,于是使用路径压缩,将复杂度维持在 $O(logn)$。在删除堆顶元素时,只需要将根的左右两棵子树合并即可,由于均满足左偏树性质,所以合并完仍为左偏树。同时记得要将删除节点$x$ 的 $root_x$ 也指向合并后的根,以保证路径压缩的正确性,同时将删除结点的信息清空。
1 int get_root(int x) 2 { 3 if(x == root[x]) return x; 4 else return root[x] = get_root(root[x]); 5 } 6 void del(int x) 7 { 8 inl[x] = false; 9 root[lc[x]] = root[rc[x]] = root[x] = merge(lc[x],rc[x]); 10 lc[x] = rc[x] = 0,dist[x] = -1; 11 }
完整代码
1 #include<bits/stdc++.h> 2 using namespace std; 3 static inline int read() 4 { 5 char c; 6 int flag = 0; 7 while(not isdigit(c = getchar())) flag = (c == '-') ? 1 : flag; 8 int x = 0; 9 while(isdigit(c)) 10 { 11 x = (x << 3) + (x << 1) + c - '0'; 12 c = getchar(); 13 } 14 return flag ? -x : x; 15 } 16 static inline void write(int x) 17 { 18 if(x >= 10) write(x / 10); 19 putchar('0' + x % 10); 20 } 21 const int N = 100000 + 10; 22 struct node 23 { 24 int pos,v; 25 friend bool operator < (node xx,node yy) { return xx.v == yy.v ? xx.pos < yy.pos : xx.v < yy.v; } 26 } val[N]; 27 int n,m; 28 bool inl[N]; 29 int lc[N],rc[N],dist[N],root[N]; 30 int merge(int x,int y) 31 { 32 if(not x or not y) return x + y; 33 if(val[y] < val[x]) swap(x,y); 34 rc[x] = merge(rc[x],y); 35 if(dist[lc[x]] < dist[rc[x]]) swap(lc[x],rc[x]); 36 dist[x] = dist[rc[x]] + 1; 37 return x; 38 } 39 int get_root(int x) 40 { 41 if(x == root[x]) return x; 42 else return root[x] = get_root(root[x]); 43 } 44 void del(int x) 45 { 46 inl[x] = false; 47 root[lc[x]] = root[rc[x]] = root[x] = merge(lc[x],rc[x]); 48 lc[x] = rc[x] = 0,dist[x] = -1; 49 } 50 signed int main() 51 { 52 dist[0] = -1; 53 n = read(),m = read(); 54 for(int i = 1;i <= n;i++) 55 { 56 val[i].v = read(); 57 val[i].pos = i,root[i] = i,inl[i] = true; 58 } 59 while(m--) 60 { 61 int opt = read(); 62 if(opt == 1) 63 { 64 int x = read(),y = read(); 65 if(not inl[x] or not inl[y]) continue; 66 x = get_root(x),y = get_root(y); 67 if(x == y) continue; 68 root[x] = root[y] = merge(x,y); 69 } 70 else 71 { 72 int x = read(); 73 if(not inl[x]) printf("-1\n"); 74 else 75 { 76 x = get_root(x); 77 write(val[x].v),putchar('\n'); 78 del(x); 79 } 80 } 81 } 82 return 0; 83 }

浙公网安备 33010602011771号