学习笔记:左偏树

左偏树是一种能够在支持在 $O(logn)$ 的时间复杂度内进行合并的满足堆性质的树,因此别名“可并堆”,具有可合并与复杂度稳定的优势。

模板题

基本概念与定义
  1. 外结点:至少有一个儿子是空节点的节点。
  2. $dist_x$ 表示以 $x$ 为根的子树内到 $x$ 距离最小的外结点的距离。特别的,对于空结点,其 $dist=-1$ 
左偏树的性质
  1. 左偏树满足小根堆性质(大根堆貌似也可以),即对于结点 $x$,总有 $v_x\leq v_{lc}$ 且 $v_x\leq v_{rc}$。

  2. 左偏树满足左偏的性质,即对于任意节点均有:$dist_{lc}\geq dist_{rc}$。
显然的结论
  1. $dist_x=dist_{rc}+1$。
  2. 一棵有 $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 }
View Code

 

删除所在堆顶元素操作

这里我们需要对于每个结点维护一个 $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 }
View Code
完整代码
 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 }
View Code

 

posted @ 2021-11-15 19:07  一程山雪  阅读(50)  评论(0)    收藏  举报