• 可并堆


    可并堆

    0x00 简介

    • 顾名思义咯,可以在O(logn)甚至O(1)时间内合并的堆
    • 也没什么好说的

    0x01 左偏树

    • 一种特殊的堆,要求左子树的距离大于右子树的距离
    • 具体实现也很奇怪,像是非旋treap那样合并
    • 最恶劣情况下的树高是O(n),暴力往上爬是不可取的,需要额外开一个并查集维护根节点
    • 我不太想讲这个,因为常数大而且不好理解
    • 不过板子我写了,可以看代码

    0x02 配对堆

    • 说实话,这个东西的思路很多人在初学二叉堆的时候都有过
    • 回忆一下向二叉堆里插入一个数的情况,我们把这个数放在size+1这个位置,然后通过一些调整维护堆的性质
    • 那为什么我们不直接像并查集一样,把这个数挂到根节点下面呢?(也可能是把根节点挂到这个点下面)
    • 这样子插入就变成O(1)了对吧?
    • 这样的话,假设我们维护的是一个小根堆,如果我们要减少某个数的值,只要把以它为根的子树拆出来,然后再和根合并就行了对吧?这个操作也是O(1)的
    • 取最大/最小元素自然不必说,也是O(1)的
    • 唯一的问题在于,如何pop出一个元素
    • 由于我们直接把数合并到根节点上,一个节点可能不止有两个儿子,这会导致如果我们想要像二叉堆那样调整,复杂度会退化成一个非常恐怖的值,求导了半天算出来是(latex O(e^{frac{n}{e}}))
    • 我们有个很妙的做法,把儿子两两合并起来,用最后的结果取代原来的根,这样做是O(n)的
    • 然而,如果我们这样做完的结果让儿子分配的更平均(儿子被均匀的分开,没有一个节点儿子特别多)了,显然下一次的合并的复杂度就会更优
    • 我们考虑一个点u,她的儿子是{1,2,3,4,5,6,7,8}
    • 然后我们做这样的操作
    • {1,2,3,4,5,6,7,8}
    • {3,4,5,6,7,8,{1,2}}
    • {5,6,7,8,{1,2},{3,4}}
    • {7,8,{1,2},{3,4},{5,6}}
    • {{1,2},{3,4},{5,6},{7,8}}
    • {{5,6},{7,8},{1,2,3,4}}
    • {{1,2,3,4},{5,6,7,8}}
    • {{1,2,3,4,5,6,7,8}}
    • 你可能已经看出来了,不断地从队首取两个元素出来合并起来,把合并完的结果丢到队尾,直到队中只有一个元素
    • 显然,这样至少会把儿子平均分成两半
    • 然后pop操作就是均摊log了
    • 然后我们就可以维护一些有趣的操作,比如在小根堆里把一个数增加一个值
    • 把她从堆里拆出来,然后把她pop出来,再合并回去
    • 总体来说是很好写的,我们采用父亲-兄弟存图的方法,对于一个点维护一下它的前驱,后继,父亲,第一个儿子,pop的时候把所有儿子拆下来丢进一个队列里,合并的时候讨论一下就行了
    • 然而树高还是最坏O(n)的,还是需要并查集辅助

    0x03 拓展!

    • 如果我们想要做一类要求合并联通块,单点修改,联通块修改,全局修改,单点查询,联通块查询,全局查询的问题(我说的就是棘手的操作),并且要求强制在线(不然直接线段树就好了)的问题,我们要怎么搞呢?
    • 我们需要一个神奇的堆套堆
    • 分别考虑每种操作和询问,注意每个操作中都要维护可并堆的根
    • 合并联通块:把两个可并堆并起来,并查集也并起来,并且对标记差分(下面会讲)
    • 单点修改:拆出来,pop,修改,并回去
    • 联通块修改:这是重点,由于我们不能做标记下传,我们需要标记永久化,标价在哪呢?不能打在可并堆上,因为堆的高度不合理,暴力往上爬是不可行的。但是如果我们放弃并查集的路径压缩,仅采用按秩合并,我们发现并查集的高度是合理的,而且形态很稳定。不妨把标记打在并查集的根上。然而合并的时候会出现问题,比如我们把u和v两个节点合并,u的size较大,一般来说我们直接让father[v]=u,size[u]+=size[v]即可,但是这样会导致u之前打的tag影响到v中的节点,所以我们做一个差分,让tag[v]-=tag[u](来自hhf的思路,%%%%)
    • 全局修改:另开一个变量专门存放全局修改
    • 单点查询:在并查集中暴力网上爬累加标记即可
    • 联通块查询:找到堆顶即可
    • 全局查询: 额外维护一个根,里面放的是所有堆的堆顶元素的值,懒的话可以像捉迷藏那样写个双堆
    • 似乎还有一个问题:单点修改似乎会破坏堆的形态啊?可能并查集的根不是堆的根啊?
    • 注意,在这里并查集只用来维护联通块,并且在堆顶维护可并堆的根的位置,实际形态和堆的形态没有关系
    • 当然,废话了那么多都是在可并堆深度O(n)的前提下的,如果写O(log)的随机堆的话直接在堆里暴力上爬就行了
    • 但为什么我棘手的操作还是只有90分啊?第三个点是什么东西啊?
    • 更新:原来是合并的时候忘了维护全局答案了。。。
    许愿弥生改二
  • 相关阅读:
    babel-loader的原理
    js弱类型转换的知识点
    个人说明
    嗯三月的小计划
    bzoj2326 [HNOI2011]数学作业
    bzoj2654 tree
    bzoj1018[SHOI2008]堵塞的交通traffic
    bzoj1937[Shoi2004]Mst 最小生成树
    一般图最大匹配-带花树-带注释模板
    FFT快速傅里叶变换-递归版-带注释模板
  • 原文地址:https://www.cnblogs.com/LoveYayoi/p/6745514.html
Copyright © 2020-2023  润新知