• Evanyou Blog 彩带


      前言

      之前我一直都这么以为:$Fibonacci$堆虽然快,但是代码复杂度反人类;二项堆打起来麻烦的死,而且又难理解又难调;综合起来还是左偏树最好!!!

      直到我知道了配对堆这个东西。。。。。。

      我$HolseLee$就是省选爆零,暴力分都拿不到,也绝不会学左偏树以外的可并堆!呵呵,配对堆真快,爆了左偏树几条街呢。

      配对堆

      以下部分内容及图片引用自:

      https://www.cnblogs.com/Paul-Guderian/p/7664366.html

      定义

      配对堆($Pairing Heap$)也是属于可并堆的一种,是一棵多叉树。也可以说它是$Fibonacci$堆的简化版本,和$Fibonacci$堆一样,其操作复杂度及其优秀,而且代码复杂度又非常低,与左偏树基本一致,是$OI$比赛的好选择。

      基本操作

      合并($merge$):$O(1)$

      插入新节点($push$):$O(1)$

      修改值($change$):$O(1)/O(log n)$

      取出堆顶元素($top$):$O(1)$

      删除堆顶元素($pop$):$O(log n)$

      

      合并

      可并堆最重要的操作,直接比较两个需要合并的堆的堆顶然后按照大小关系连边就行了。复杂度低到惊人的$O(1)$;

      插入新节点

      可以把新节点看作一个只有一个元素的堆,再与当前堆合并就行了。

      取出堆顶元素

      直接返回堆顶值即可。

      

      修改值

      如何修改一个点的值呢?首先将这个点与父节点之间的连边断开,即令$fa[u]=0$,(如果用邻接链表存边不方便断边就可以不断,因为除了连边以外,判断父子关系还需要子节点的$fa[]$指向父节点,因此不断边实际上也不会有问题)然后修改值即可。

      断掉与父亲的连边后,相当于形成两个堆,接下来进行一次$merge$操作就好了。可以发现这个操作的时间复杂度是$O(1)$,但有资料认为这个操作可能会破坏配对堆应有的结构(这"应有"的结构在下文会体现出来,它是$pop$操作复杂度是$O(log n)$而不是$O(n)$的重要保证),结构改变后就会影响$pop$的复杂度,使其向$O(n)$退化,因此计算后认定其实修改操作从时间复杂度贡献分析来看,可能是$O(log n)$而不是$O(1)$。

      不过反正这个操作比较少用就是了。

      

      删除堆顶元素

      可以发现,以上的操作基本上就像蛇皮怪一样的瞎$merge$连个边就了事了,所以复杂度就全部承受在这个操作上了(爱谴责人士表示强烈$pop$)

      现在需要删除根节点,如果直接把根节点删掉然后把它的子节点一个个连起来的话,可能会使新的根节点有多个子节点然后影响后面的复杂度,使得后面$pop$操作复杂度退化成$O(n)$。较优秀的方法应该是将子节点两两$merge$,直到全部合并在一起为止。这样可以保证后面的复杂度不会退化。

      

      

      模板代码

      照着网上的大佬自己瞎几把打了一份,打的不好看将就着看吧。

      Code:

      

    //It is made by HosleLee on 25th Oct 2018
    //Pairing_Heap
    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=1e5+7;
    int n,m,val[N],head[N],cnte,fa[N];
    struct Edge { int to,nxt; }e[N<<1];
    queue<int>q;
    
    inline int read()
    {
        char ch=getchar(); int x=0; bool flag=false;
        while( ch<'0' || ch>'9' ) {
            if( ch=='-' ) flag=true; ch=getchar();
        }
        while( ch>='0' && ch<='9' ) {
            x=x*10+ch-'0'; ch=getchar();
        }
        return flag ? -x : x;
    }
    
    inline int find(int x)
    {
        if( fa[x]==-1 ) return 0;
        while( fa[x]!=x ) x=fa[x];
        return x;
    }
    
    inline void add(int x,int y)
    {
        e[++cnte].to=y, e[cnte].nxt=head[x];
        head[x]=cnte; fa[y]=x;
    }
    
    inline int neo(int x,int i)
    {
        val[i]=x; return i;
    }
    
    inline int merge(int x,int y)
    {
        if( !x || !y ) return x+y;
        if( val[x]<val[y] || (val[x]==val[y] && x<y ) )  return add(x,y),x;
        return add(y,x),y;
    }
    
    inline void pop(int rt)
    {
        int x,y;
        for(int i=head[rt]; i; i=e[i].nxt)
        if( fa[y=e[i].to]==rt ) q.push(y), fa[y]=y;
        fa[rt]=-1;
        while( q.size()>1 ) {
            x=q.front(), q.pop();
            y=q.front(), q.pop();
            q.push(merge(x,y));
        }
        if( !q.empty() ) q.pop();
    }
    
    inline void top(int x)
    {
        if( !x ) { printf("-1
    "); return; }
        printf("%d
    ",val[x]);
        pop(x);
    }
    
    int main()
    {
        n=read(); m=read();
        for(int i=1; i<=n; ++i) fa[i]=neo(read(),i);
        int opt;
        for(int i=1; i<=m; ++i) {
            opt=read();
            if( opt==1 ) merge(find(read()),find(read()));
            else top(find(read()));
        }
        return 0;
    }

      总结

      学完配对堆以后,我突然发现好像左偏树除了可持久化以外貌似一无是处了(复杂度完全被吊打233333)??????

      当然不是,因为左偏树结构非常稳定,方便套用其他不常用的一些操作,所以根据情况选择算法就行了,各种算法都各有各的优劣。(顺便推荐一波我的另一篇左偏树的博客23333

      最后附上一张各种堆比较的表格:

      

  • 相关阅读:
    [CF833B] The Bakery
    [CF1203F1] Complete the Projects
    [CF354C] Vasya and Beautiful Arrays
    [CF7D] Palindrome Degree
    [CF1466F] Euclid's nightmare
    【转】node-webkit:开发桌面+WEB混合型应用的神器
    (转)background-position—CSS设置背景图片的位置
    (转)国外漂亮表格连接地址
    URL转义
    T-SQL实用查询之常用SQL语句
  • 原文地址:https://www.cnblogs.com/cytus/p/9477876.html
Copyright © 2020-2023  润新知