首页 > 代码库 > 【模板】左偏树

【模板】左偏树

洛谷模板题

 

一听左偏树这个名字就感觉左偏。。

左偏树是什么,好像就是个堆,大根堆或小根堆,可以支持合并,取堆顶元素,删除堆顶元素,插入元素的操作。

 

一些说明:

左偏树节点除了应有的东西,还有键值和距离,键值用于比较大小,距离是什么?

距离是这样定义的:

节点i称为外节点(external node),当且仅当节点i的左子树或右子树为空 ( left(i) = NULL或right(i) = NULL );节点i的距离(dist(i))是节点i到它的后代中,最近的外节点所经过的边数。特别的,如果节点i本身是外节点,则它的距离为0;而空节点的距离规定为-1 (dist(NULL) = -1)。一棵左偏树的距离,指的是该树根节点的距离。

额。。多看几遍才看懂。

 

左偏树具体有一些性质:

[性质1] 节点的键值小于或等于它的左右子节点的键值。(键值就是点权)——堆的性质

[性质2] 节点的左子节点的距离不小于右子节点的距离。——左偏

[性质3] 节点的距离等于它的右子节点的距离加1。(因为左偏,所以右儿子距离小)

[引理1] 若左偏树的距离为一定值,则节点数最少的左偏树是完全二叉树。  

[定理1] 若一棵左偏树的距离为k,则这棵左偏树至少有2k+1-1个节点。

[性质4] 一棵N个节点的左偏树距离最多为?log(N+1)? -1。(这是什么鬼?)

 

根据性质就可以理解左偏树操作的具体步骤了。

合并A和B:1.如果有一个树为空就返回另一个

      2.假定root(A).w < root(B).w, 否则交换A和B,把root(A)作为新树的根

      3.合并right(A)和B, 继续步骤2

      4.由于合并之后right(A)的距离可能会变大, 如果变大就交换right(A)和left(A)

      5.由于right(A)距离改变,A的距离也会变,更新dis(A) = dis(right(A)) + 1

技术分享
 1 //合并以 x, y 为根的堆 
 2 inline int merge(int x, int y)
 3 {
 4     if(!x || !y) return x + y;
 5     if(h[x].w > h[y].w) swap(x, y);
 6     h[x].r = merge(h[x].r, y);
 7     if(h[h[x].l].d < h[h[x].r].d) swap(h[x].l, h[x].r);
 8     h[x].d = h[h[x].r].d + 1;
 9     return x;
10 }
View Code

删除堆顶元素:删除后合并他的两个儿子即可

技术分享
1 inline void pop(int x)
2 {
3     int l = h[x].l, r = h[x].r;
4     h[x].d = h[x].l = h[x].r = h[x].w = 0;//可有可无的东西 
5     f[x] = f[l] = f[r] = merge(l, r);
6     //因为有的节点的父亲指向当前堆顶元素 x, 所以也修改堆顶元素的父亲
7 }
View Code

取出堆顶元素:更智障

技术分享
1 inline int top(int x)
2 {
3     return h[x].w;
4 }
View Code

还有一些操作用到了并查集,具体细节看完整代码。

技术分享
 1 #include <iostream>
 2 #include <cstdio>
 3 
 4 using namespace std;
 5 
 6 int n, m;
 7 int f[100001];//表示第i个数所在堆的堆顶 
 8 bool b[100001];//表示是否被删除 
 9 struct heap
10 {
11     int l, r, w, d;
12 }h[100001];//小根堆 
13 
14 //合并以 x, y 为根的堆 
15 inline int merge(int x, int y)
16 {
17     if(!x || !y) return x + y;
18     if(h[x].w > h[y].w) swap(x, y);
19     h[x].r = merge(h[x].r, y);
20     if(h[h[x].l].d < h[h[x].r].d) swap(h[x].l, h[x].r);
21     h[x].d = h[h[x].r].d + 1;
22     return x;
23 }
24 
25 inline void pop(int x)
26 {
27     int l = h[x].l, r = h[x].r;
28     h[x].d = h[x].l = h[x].r = h[x].w = 0;//可有可无的东西 
29     f[x] = f[l] = f[r] = merge(l, r);
30     //因为有的节点的父亲指向当前堆顶元素 x, 所以也修改堆顶元素的父亲
31 }
32 
33 inline int top(int x)
34 {
35     return h[x].w;
36 }
37 
38 inline int find(int x)
39 {
40     return x == f[x] ? x : f[x] = find(f[x]);
41 }
42 
43 int main()
44 {
45     int i, j, x, y, c, fx, fy;
46     scanf("%d %d", &n, &m);
47     for(i = 1; i <= n; i++) scanf("%d", &h[i].w), f[i] = i;
48     for(i = 1; i <= m; i++)
49     {
50         scanf("%d", &c);
51         if(c == 1)
52         {
53             scanf("%d %d", &x, &y);
54             if(b[x] || b[y]) continue;//如果有一个数被删除 
55             fx = find(x);
56             fy = find(y);
57             if(fx == fy) continue;//在同一个堆中 
58             f[fx] = f[fy] = merge(fx, fy);//合并 
59         }
60         else
61         {
62             scanf("%d", &x);
63             if(b[x]) printf("-1\n");
64             else
65             {
66                 fx = find(x);
67                 printf("%d\n", h[fx].w);
68                 b[fx] = 1;
69                 pop(fx);
70             }
71         }
72     }
73     return 0;
74 }
View Code

 

【模板】左偏树