左偏树 顾名思义 向左偏的树 (原题入口

它有啥子用呢??? 当然是进行堆的合并啦2333
普通堆的合并其实是有点慢的(用优先队列的话 只能 一个pop 一个push 来操作 复杂度就是O(n log n))
而左偏树就特别快 (一个堆可以一次性合并 复杂度只需O(log n) )

左偏树共有 3条性质: (来自于百度百科)
[性质1] 节点的键值小于或等于它的左右子节点的键值。 //这个性质普通堆都具有 不用深究 学过二叉堆的都知道
[性质2] 节点的左子节点的距离不小于右子节点的距离。
这条就是它为啥叫左偏树的原因,接下来讲讲啥是距离。 距离就是 一个节点 到它后代最近的外节点所经过的边数
外节点又是啥呢 就是左右子树有一个为空(NULL) 的节点。由定义易得,外节点的距离就是0。
由这两条性质,我们可以得出左偏树的定义:左偏树是具有左偏性质的堆有序二叉树。
[性质3] 节点的距离等于它的右子节点的距离加1。
这个是为啥得出呢 由性质2可以推出来 对于一个外节点 它的左子节点距离不小于右子节点距离的话 那它的右子树必为空
我们又从下向上推 它右子节点距离更小的话 那么 它显然经过右子节点能到它的最近外节点

[引理1] 若左偏树的距离为一定值,则节点数最少的左偏树是完全二叉树。
[定理1] 若一棵左偏树的距离为k,则这棵左偏树至少有2^(k+1)-1个节点。
[性质4] 一棵N个节点的左偏树距离最多为log(N+1)-1。
这几条性质都可以由[性质2]推出 然而我并不知道有啥用qwq 有兴趣的可以去看看大牛的博客

下一段也是摘抄自百度百科 解释为啥它合并这么快:
我们的印象中,平衡树是具有非常小的深度的,这也意味着到达任何一个节点所经过的边数很少。
左偏树并不是为了快速访问所有的节点而设计的,它的目的是快速访问最小节点以及在对树修改后快速的恢复堆性质。
从图中我们可以看到它并不平衡,由于性质2的缘故,它的结构偏向左侧,不过距离的概念和树的深度并不同,
左偏树并不意味着左子树的节点数或是深度一定大于右子树。

它最重要的代码就是合并 我按我蒟蒻的理解随便讲讲算了(好敷衍。。):

1.首先如果合并一个实树(自定义。。就是有节点的树) 和一个空树 直接返回那个实树
2.其次我们是要求小根堆 就要将 key 小的节点放在根节点 我们默认为A 所以如果 key(A) > key(B) 那么我们就交换
3.之后我们就将 B 接到 A 的右子树上面 (至于为啥不能是左子树 我也不知道QAQ 不是左偏树么。。)
4.然后看看 A左右子树的距离 如果左子树距离要小于右子树 那么直接交换左右子树 为了满足[性质2]
5.接下来我们更新 A 的距离 如果为外节点(右子树为空) 那么直接为0 否则 为 右子树距离 + 1

这是是用递归实现的合并 每次合并后 应该返回合并后的根节点下标 (也就是我写的小merge,大Merge是合并两个堆的根的)

看看代码。。。

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <algorithm>
  6. #include <cctype>
  7. #include <iostream>
  8. #define For(i, l, r) for(int i = (l); i <= (int)(r); ++i)
  9. #define Fordown(i, r, l) for(int i = (r); i >= (int)(l); --i)
  10. #define Set(a, v) memset(a, v, sizeof(a))
  11. using namespace std;
  12.  
  13. inline int read(){
  14. int x = , fh = ; char ch;
  15. for(; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -;
  16. for(; isdigit(ch); ch = getchar()) x = (x<<) + (x<<) + (ch^'');
  17. return x * fh;
  18. }
  19.  
  20. struct node {
  21. int lc, rc, val; //分别是left_child左儿子, right_child右儿子, value 键值
  22. };
  23. const int max_node = ; //最大子节点个数
  24. node lt[max_node]; //leftist_tree 左偏树
  25. int dist[max_node], fa[max_node]; //左偏树距离dist:到最右叶子节点边的条数
  26. //并查集父亲数组fa
  27.  
  28. void make_tree (int x) {
  29. lt[x].lc = lt[x].rc = dist[x] = ; //清空它的左右儿子和距离(新建一个树)
  30. }
  31.  
  32. int merge (int a, int b) { //合并两个可以合并的树 返回合并后的父节点
  33. if (a == ) return b; //a为空 返回b
  34. if (b == ) return a; //b为空 返回a
  35. if (lt[a].val > lt[b].val) swap(a, b); //将根节点设为a(值较小的那个)
  36. lt[a].rc = merge(lt[a].rc, b); fa[lt[a].rc] = a; //将b合并到a的右子树上 并将右子树父亲设为a
  37. if (dist[lt[a].rc] > dist[lt[a].lc]) swap(lt[a].lc, lt[a].rc); //右子节点距离大于左子节点 不符合左偏树性质则交换左右子树
  38. if (lt[a].rc == ) dist[a] = ; //右子树为空 距离为0 即直接到自己
  39. else dist[a] = dist[lt[a].rc] + ; //否则按照性质 距离为右子节点的距离+1
  40. return a; //返回a节点
  41. }
  42.  
  43. int find (int x) {
  44. return fa[x] = fa[x] == x ? x : find(fa[x]); //并查集操作
  45. }
  46.  
  47. void Merge (int a, int b) { //合并两个数
  48. int root_a = find(a), root_b = find(b);
  49. if (root_a == root_b) return ; //在同一个堆里就不合并
  50. if (lt[a].val == || lt[b].val == ) return ; //这个数已被删掉也不合并
  51. // cout << "merge: " << root_a << ' ' << root_b << endl;
  52. int tmp = merge(root_a, root_b);
  53. fa[root_a] = fa[root_b] = tmp; //将两个节点父亲设为合并后的父亲
  54. }
  55.  
  56. void delete_min (int a) {
  57. int root_a = find(a); //找到堆顶
  58. if (lt[a].val == ) {printf ("-1\n"); return;} //已被删除 输出-1
  59. // cout << "find: " << root_a << endl;
  60. printf ("%d\n", lt[root_a].val); //输出堆顶的值
  61. int tmp = merge (lt[root_a].lc, lt[root_a].rc); //合并这个堆顶的左右子树
  62. fa[lt[root_a].lc] = fa[lt[root_a].rc] = fa[root_a] = tmp; //将左右子树和原来根节点的父亲设为它们合并后的父亲
  63. lt[root_a].lc = lt[root_a].rc = lt[root_a].val = ; //删除堆顶
  64. }
  65.  
  66. int main(){
  67. int n = read(), m = read();
  68. For (i, , n) {
  69. lt[i].val = read();
  70. make_tree(i);
  71. fa[i] = i;
  72. }
  73. while (m--) {
  74. int opt = read();
  75. if (opt == ) {
  76. int a = read(), b = read();
  77. Merge(a, b);
  78. }
  79. else {
  80. int a = read();
  81. delete_min(a);
  82. }
  83. }
  84. }

Update 2018-4-8

当年的代码写的好丑啊qwq 再挂一个新的码风的代码...

  1. #include <bits/stdc++.h>
  2. #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
  3. #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
  4. #define Set(a, v) memset(a, v, sizeof(a))
  5. using namespace std;
  6.  
  7. void File() {
  8. #ifdef zjp_shadow
  9. freopen ("P3377.in", "r", stdin);
  10. freopen ("P3377.out", "w", stdout);
  11. #endif
  12. }
  13.  
  14. const int N = ;
  15.  
  16. int fa[N];
  17. int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
  18.  
  19. struct Lefist_Tree {
  20. int val[N], ls[N], rs[N], dis[N];
  21.  
  22. int Merge(int a, int b) {
  23. if (!a || !b) return a | b;
  24. if (val[a] > val[b]) swap(a, b);
  25. rs[a] = Merge(rs[a], b); fa[rs[a]] = a;
  26. if (dis[rs[a]] > dis[ls[a]]) swap(ls[a], rs[a]);
  27. if (!ls[a]) dis[a] = ;
  28. else dis[a] = dis[ls[a]] + ;
  29. return a;
  30. }
  31.  
  32. void Opt_Merge(int a, int b) {
  33. int rta = find(a), rtb = find(b);
  34. if (rta == rtb) return ;
  35. if (!val[a] || !val[b]) return ;
  36. fa[rta] = fa[rtb] = Merge(rta, rtb);
  37. }
  38.  
  39. int Opt_Delete(int a) {
  40. if (!val[a]) return -;
  41. int rta = find(a), res = val[rta];
  42. int tmp = Merge(ls[rta], rs[rta]);
  43. fa[ls[rta]] = fa[rs[rta]] = fa[rta] = tmp;
  44. ls[rta] = rs[rta] = val[rta] = ;
  45. return res;
  46. }
  47.  
  48. void Make_Tree(int x) {
  49. ls[x] = rs[x] = val[x] = ;
  50. }
  51. } T;
  52.  
  53. int n, m;
  54.  
  55. int main () {
  56. File();
  57. scanf ("%d%d", &n, &m);
  58. For (i, , n) T.Make_Tree(i), scanf ("%d", &T.val[i]), fa[i] = i;
  59. For (i, , m) {
  60. int opt;
  61. scanf ("%d", &opt);
  62. if (opt == ) { int a, b; scanf ("%d%d", &a, &b); T.Opt_Merge(a, b); }
  63. else { int a; scanf ("%d", &a); printf ("%d\n", T.Opt_Delete(a)); }
  64. }
  65. return ;
  66. }

luogu【P3377】 【模板】左偏树的更多相关文章

  1. 洛谷 P3377 模板左偏树

    题目:https://www.luogu.org/problemnew/show/P3377 左偏树的模板题: 加深了我对空 merge 的理解: 结构体的编号就是原序列的位置. 代码如下: #inc ...

  2. 2021.08.01 P3377 左偏树模板

    2021.08.01 P3377 左偏树模板 P3377 [模板]左偏树(可并堆) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include<iostream> ...

  3. 洛谷 P3377 【模板】左偏树(可并堆)

    洛谷 P3377 [模板]左偏树(可并堆) 题目描述 如题,一开始有N个小根堆,每个堆包含且仅包含一个数.接下来需要支持两种操作: 操作1: 1 x y 将第x个数和第y个数所在的小根堆合并(若第x或 ...

  4. 模板 可并堆【洛谷P3377】 【模板】左偏树(可并堆)

    P3377 [模板]左偏树(可并堆) 如题,一开始有N个小根堆,每个堆包含且仅包含一个数.接下来需要支持两种操作: 操作1: 1 x y 将第x个数和第y个数所在的小根堆合并(若第x或第y个数已经被删 ...

  5. 洛谷 - P3377 - 【模板】左偏树(可并堆) - 左偏树 - 并查集

    https://www.luogu.org/problemnew/show/P3377 左偏树+并查集 左偏树维护两个可合并的堆,并查集维护两个堆元素合并后可以找到正确的树根. 关键点在于删除一个堆的 ...

  6. 洛谷P3377 【模板】左偏树(可并堆) 题解

    作者:zifeiy 标签:左偏树 这篇随笔需要你在之前掌握 堆 和 二叉树 的相关知识点. 堆支持在 \(O(\log n)\) 的时间内进行插入元素.查询最值和删除最值的操作.在这里,如果最值是最小 ...

  7. [洛谷P3377]【模板】左偏树(可并堆)

    题目大意:有$n$个数,$m$个操作: $1\;x\;y:$把第$x$个数和第$y$个数所在的小根堆合并 $2\;x:$输出第$x$个数所在的堆的最小值 题解:左偏树,保证每个的左儿子的距离大于右儿子 ...

  8. 题解 P3377 【【模板】左偏树(可并堆)】

    所谓的左偏树,是一种可并堆的实现. 这种数据结构能够支持高效的堆合并,但是不支持查询节点等操作,因此不同于平衡树,它的结构是不平衡的. 左偏树满足如下两条基本性质: 1. 堆的性质 这也就是说左偏树每 ...

  9. P3377 【模板】左偏树(可并堆) 左偏树浅谈

    因为也是昨天刚接触左偏树,从头理解,如有不慎之处,跪请指教. 左偏树: 什 么是(fzy说)左偏树啊? 前置知识: 左偏树中dist:表示到右叶点(就是一直往右下找,最后一个)的距离,特别的,无右节点 ...

随机推荐

  1. 一个脚本从git上pull 并更新到服务器

    #/bin/bash cd /src/pid01-beta/ echo "update pid01.." git pull ]; then echo "update pi ...

  2. Spring框架系列(二)之Bean的注解管理

    微信公众号:compassblog 欢迎关注.转发,互相学习,共同进步! 有任何问题,请后台留言联系! 1.Spring中的两种容器 在系列(一)中我们已经知道,Spring 是管理对象的容器,其中有 ...

  3. c的文件流读取

    strtok(数组,分隔符); atof(数组)返回值为转换后的数字; fgets(数组指针,长度,文件句柄); 整整花了两天啊

  4. Python中List和Tuple类型

    a = 'python' print('hello,', a or 'world') b = '' print ('hello,', b or 'world') print('------------ ...

  5. 使用Git的hook实现代码的自动部署

    这个功能非常的好用,可以省去诸多麻烦!我自己也是摸索了好久,才完全掌握的.希望能对大家有所帮助! 1,首先在我的阿里云服务器上已经创建好了一个代码远程的管理仓库,/srv/cmp.git 2, 在服务 ...

  6. 1 Python数据类型--

    常见的Python数据类型: (1)数值类型:就是平时处理的数字(整数.浮点数) (2)序列类型:有一系列的对象并排或者排列的情况.如字符串(str),列表(list),元组(tuple)等 (3)集 ...

  7. UVA - 1631 Locker 记忆化搜索

    题意:给定两个密码串,每次可以让1~3个相邻的密码向上或者向下滚动,每个密码是 ,问最少需要多少次滚动可以让原串成为目标串? 思路:假设当前要让第i位密码还原,我们可以同时转动,不同的转动方式会影响后 ...

  8. Kotlin——基础的函数/方法详解

    对于Kotlin中的函数来说,和JavaScript或者Lua这些语言很像,它有着这些语言的特性.但是也与这些语言就着许多不同之处.或许你更了解Java语言,然而对于Java语言来说,它不是不是闭包这 ...

  9. word自动备份,word误删内容恢复

    有个问题时长困扰着我,就是一次不小心把word里面的一部分内容误删了之后,又手残点击ctrl+s给保存了,要是立即ctrl+z还能撤销,可要是关闭了word才想起来撤销就来不及啦,现在终于找到解决的办 ...

  10. LVDS/DVI/HDMI Interface

    数字视频信号 以SXGA为例,其时序如下: 垂直:         水平: 图中DSPTMG为使能信号,VSYNC为场同步信号,HSYNC为行同步信号.在行场的消隐期(T1与T7),DSPTMG为低电 ...