可并堆讲解

可并堆讲解

  注:本篇博客以大根堆为例。

  可并堆,顾名思义,就是可以合并的堆。堆满足一个性质,就是当前节点,都大于或者等于他的所有子树上的节点,自然在这里我所讲的是结点的权值。显而易见,既然可并堆是堆的一种,容易推出,可并堆也满足这个性质。

  现在思考一个问题,当题目里需要合并两个堆的时候,该如何合并呢?如果只是普通的堆的话,我们可以运用启发式合并的思想,每一次把size小的堆的点暴力插入到size大的堆里面,这个方法十分不优秀,时间复杂度是O(nlog2n)。所以我们会用可并堆来实现这个问题。

  可并堆是一种运用到左偏树思想的堆,何为左偏树?左偏树顾名思义就是偏向左面的树,如左图就是一棵左偏树,这棵树明显满足一个性质。我们定义dis[p]为从p节点出发可以向右走的最大步数,从右图中可以看到,当前点的左儿子的dis值都比右儿子的dis值大,只要满足这个性质就是左偏树。

  学过左偏树之后,我们就可以学习可并堆了,可并堆的难点就是合并,那么应该如何合并呢?为了能保证时间复杂度,我们每一次合并都应该把新的节点放在当前的节点的右儿子上,这个是一个递归的过程,每一次把当前的两个节点进行比较,留下权值大的点,然后递归下去把另一个点和留下的点的右儿子进行比较,如此下去,进行合并。当然每一次回溯之前,我们都需要判断当前节点的左右两个儿子的dis值,如果右儿子的dis值大于左儿子的dis值,则交换左右儿子。

int merge(int x,int y)
{
    if(!x) return y;
    if(!y) return x;
    if(num[x]<num[y]) swap(x,y);
    son[x][1]=merge(son[x][1],y);
    if(dis[son[x][1]]>dis[son[x][0]])
        swap(son[x][1],son[x][0]);
    dis[x]=dis[son[x][1]]+1;
    return x;
}
//son[p][0]表示p号节点的左儿子
//son[p][1]表示p号节点的右儿子
//num[p]表示p号节点的权值

  下面就是删除节点(最大值),只需要把他的两个儿子合并就好啦,是不是很简单?代码就不附了,具体题目具体分析。

  大致就是这样,不会的可以评论发问题,我会解答。

题目(我会不断更新)

  bzoj1455&&luogu2713罗马游戏:http://www.cnblogs.com/yangsongyi/p/8893005.html

  APIO2012派遣:http://www.cnblogs.com/yangsongyi/p/8921448.html

  JLOI2015城池攻占:http://www.cnblogs.com/yangsongyi/p/9046913.html

posted @ 2018-04-20 18:53  Yang1208  阅读(4476)  评论(3编辑  收藏  举报