左偏树总结

既然新学了左偏树,那我就来写一些学了左偏树之后的总结吧

Part 1 左偏树是干嘛的

首先,它支持的是两个堆的合并过程。那么最容易想到的是把一个堆的元素全部弹出,一个一个加入另一个堆中,就合并了。显然这样合并的复杂度是O(n)的,再加上程序的其他部分,很慢。我们就考虑让复杂度减小到O(logn),很恐怖,没错,这就是左偏树存在的意义。

那为什么要叫左偏树,顾名思义,往左偏的树,我们首先命名一个dis[],ls[],rs[],表示一个节点到他下方最近的空节点的距离,左孩子,右孩子。显然,为了维护左偏那么dis[ls[i]]一定要维护大于dis[ys[i]],如果小于了,就换。并且为了保证这个点的dis是离空节点最近的,显然是维护dis[i]=dis[rs[i]]+1。至于为什么一定要左偏,是因为我们之后的操作是往右合并,所以操作次数会减少。(具体看后文把)

Part 2 实现过程(以大根堆为例,小根堆类推)

·合并过程

我们来模拟一下,如果两个堆A,B要合并,那么新的堆顶元素一定是两个堆堆顶元素的最大值。这是必然的,那么我们在比较完之后,把两个堆顶的最大值放在A堆的堆顶,再把小的那一个放在B堆的堆顶,此时A堆的堆顶已经对后面的合并没有影响了,就可以继续合并更小的值了,我们考虑把B堆往A堆的右孩子决定( 需要注意:我们在整个左偏树合并过程中,都直接以堆顶的编号作为堆的序号,所以到了右孩子,就相当于到了以右孩子为堆顶的一个堆),这样不就是一个递归嘛,不停地比较,然后合并新堆和右孩子。

    int Merge(rg int A,rg int B)//合并A堆和B堆  
    {  
        if(!A||!B)return A+B;//如果有一个堆空了(堆顶元素编号为0),就返回另一个堆顶  
        if(key[A]<key[B])swap(A,B);//维护大根堆  
        rs[A]=Merge(rs[A],B);//新的A的右孩子是A的右孩子和新B合并的结果  
        if(dis[ls[A]]<dis[rs[A]])swap(ls[A],rs[A]);//维护左偏  
        dis[A]=dis[rs[A]]+1;//维护当前节点的dis值  
        return A;//返回堆顶元素  
    }  

·删除过程

很容易想到,删除堆顶元素,不就是把堆顶元素的左孩子和右孩子合并嘛

    int Delete(rg int A)//删除A堆的堆顶元素  
    {  
        return Merge(ls[A],rs[A]);//合并A的左孩子和右孩子,并返回堆顶  
    }  

Part 3 几道左偏树的题

T1 洛谷P1552 [APIO2012]派遣

我写的题解emmm:[洛谷P1552] [APIO2012]派遣

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

T3 洛谷P1456 Monkey King

//这两道实在不行可以去看看洛谷的题解

T4 洛谷P3261 [JLOI2015]城池攻占

我写的题解emmm:[洛谷P3261] [JLOI2015]城池攻占

最后我再推荐一位大佬的博客,我就是和他学的左偏树租酥雨的左偏树

posted @ 2018-03-24 19:36  Eternal风度  阅读(208)  评论(0编辑  收藏  举报
/*自定义地址栏logo*/