【左偏树(可并堆)模板】

左偏树:

我想您应该会二叉堆吧,它包含三个操作,这里与小根堆为例:
1 查询最小值
2 弹出最小值
3 插入一个值
可以使用 STL priority _ queue 实现,也可以用pb_ds中的库实现,当然也可以手写反正我不会,笔者是用的系统堆(包含在库 algorithm 中),和手写堆速度相差无几,在此推荐给大家:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<map>
#include<set>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
#define RG register
#define file(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout);
using namespace std;

inline int gi(){
    int data=0,w=1;
    char ch=0;
    while(ch!='-'&&(ch>'9'||ch<'0')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') data=data*10+ch-'0',ch=getchar();
    return data*w;
}
inline bool cmp(const int &a,const int &b){return a>b;}//cmp存小根堆
struct heap{
    int a[510*510*16];int c;
    heap(){c=0;}
    inline void push(int x){c++;a[c]=x;push_heap(&a[1],&a[c+1],cmp);}
    inline int top(){return a[1];}
    inline void pop(){pop_heap(&a[1],&a[c+1],cmp);c--;}
    inline bool empty(){return c==0;}

}Q;
int main(){
    RG int n=gi();
    while(n--){
    int opt=gi();
    if(opt==1) Q.push(gi());
    if(opt==2) printf("%d\n",Q.top());
    if(opt==3) Q.pop();
    }
    return 0;
}

但是我要讲的并不是二叉堆,所以一笔带过(不懂的可以去网上搜,一点也不难)。

下面正式进入话题:

什么叫 呢?顾名思义,就是可以合并的堆,没错,可并堆就是可以合并的堆,它支持二叉堆的操作,而且还支持 log(n) 把两个堆合并。
此时, STL 就不行了( pb _ ds 还是可以滴)。所以我们要手写可并堆,而我们今天要讲的左偏树( LiftistTree ),就是其中的一种。
什么是左偏树咧?首先,我们又顾名思义一下,它很明显一棵树。没错!其实它就是一棵树,而且是颗二叉树。
它的节点上存 4 个值:左、右子树的地址,权值,距离。权值就是丢进去的数值。距离表示这个节点到它子树里面最近的叶子节点的距离。叶子节点距离为0
而既然它是一种特殊的数据结构,那自然有它自己的性质。下面介绍左偏树的 4 个性质,自己仔细想一想(还是以小根堆为例):
1节点的权值小于其左右儿子的权值
2 节点左儿子的距离不小于右儿子的距离(注意只是距离,不代表节点数和深度也是如此)
3 节点的距离等于右儿子的距离+ 1
4一个 n 个节点的左偏树距离最大为log(n+1)1
性质讲完了,所以——

下面是操作:

1 合并:
我们假设A的根节点小于等于B的根节点(否则交换A,B),把A的根节点作为新树C的根节点,剩下的事就是合并A的右子树和B了。合并了A的右子树和B之后,A的右子树的距离可能会变大,当A的右子树 的距离大于A的左子树的距离时,性质二会被破坏。在这种情况下,我们只须要交换A的右子树和左子树就能满足条件。
而且因为A的右子树的距离可能会变,所以要更新A的距离=右儿子距离+1。这样就合并完了。
代码:

int merge(int x,int y){
    if(x==0||y==0) return x+y;
    if(val[x]>val[y]||(val[x]==val[y]&&x>y)) swap(x,y);
    ch[x][1]=merge(ch[x][1],y);
    fa[ch[x][1]]=x;
    if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][0],ch[x][1]);
    dis[x]=dis[ch[x][1]]+1;
    return x;
}

2 删除,取出最小值
为什么放一起??
因为太容易了。。。
删除:把两颗子树一合并,就完了。
取出:找根节点即可。

inline void pop(int a){
    int x=getf(a);
    val[x]=-1;
    fa[ch[x][0]]=fa[ch[x][1]]=0;
    merge(ch[x][0],ch[x][1]);
}
inline int top(int a){return val[getf(a)];}

完整代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#include<ctime>
#define RG register
#define file(x) freopen(x".in","r",stdin);
using namespace std;

inline int gi(){
    RG int data=0,w=1;
    RG char ch=0;
    while(ch!='-'&&(ch>'9'||ch<'0')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') data=(data<<1)+(data<<3)+(ch^48),ch=getchar();
    return w*data;
}
#define N 100010
class Liftist_Tree{
    public:
        int ch[N][2],val[N],dis[N],fa[N];
        Liftist_Tree(){memset(val,-1,sizeof(val));}
        int merge(int x,int y){
            if(x==0||y==0) return x+y;
            if(val[x]>val[y]||(val[x]==val[y]&&x>y)) swap(x,y);
            ch[x][1]=merge(ch[x][1],y);
            fa[ch[x][1]]=x;
            if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][0],ch[x][1]);
            dis[x]=dis[ch[x][1]]+1;
            return x;
        }
        inline int top(int a){return val[getf(a)];}
        inline void pop(int a){
            int x=getf(a);
            val[x]=-1;
            fa[ch[x][0]]=fa[ch[x][1]]=0;
            merge(ch[x][0],ch[x][1]);
        }
        inline int getf(int x){
            while(fa[x]) x=fa[x];
            return x;
        }
}t; 
int main(){
    int n=gi();RG int m=gi();
    t.dis[0]=-1;
    for(RG int i=1;i<=n;i++) t.val[i]=gi();
    while(m--){
        int opt=gi();
        if(opt==1){
            int x=gi(),y=gi();
            if(t.val[x]==-1||t.val[y]==-1) continue;
            if(x==y) continue;
            int a=t.getf(x),b=t.getf(y);
            t.merge(a,b);
        }
        if(opt==2){
            int x=gi();
            if(t.val[x]==-1) puts("-1");
            printf("%d\n",t.top(x));
            t.pop(x);
        }
    }
    return 0;
}

Tips:评测地址

ACM 算法模板集 Contents 一. 常用函数与STL 二. 重要公式与定理 1. Fibonacci Number 2. Lucas Number 3. Catalan Number 4. Stirling Number(Second Kind) 5. Bell Number 6. Stirling's Approximation 7. Sum of Reciprocal Approximation 8. Young Tableau 9. 整数划分 10. 错排公式 11. 三角形内切圆半径公式 12. 三角形外接圆半径公式 13. 圆內接四边形面积公式 14. 基础数论公式 三. 大数模板,字符读入 四. 数论算法 1. Greatest Common Divisor最大公约数 2. Prime素数判断 3. Sieve Prime素数筛法 4. Module Inverse模逆元 5. Extended Euclid扩展欧几里德算法 6. Modular Linear Equation模线性方程(同余方程) 7. Chinese Remainder Theorem中国余数定理(互素于非互素) 8. Euler Function欧拉函数 9. Farey总数 9. Farey序列构造 10. Miller_Rabbin素数测试,Pollard_rho因式分解 五. 图论算法 1. 最小生成树(Kruscal算法) 2. 最小生成树(Prim算法) 3. 单源最短路径(Bellman-ford算法) 4. 单源最短路径(Dijkstra算法) 5. 全源最短路径(Folyd算法) 6. 拓扑排序 7. 网络预流和最大流 8. 网络最小费用最大流 9. 网络最大流(高度标号预流推进) 10. 最大团 11. 二分图最大匹配(匈牙利算法) 12. 带权二分图最优匹配(KM算法) 13. 强连通分量(Kosaraju算法) 14. 强连通分量(Gabow算法) 15. 无向图割边割点和双连通分量 16. 最小树形图O(N^3) 17. 最小树形图O(VE) 六. 几何算法 1. 几何模板 2. 球面上两点最短距离 3. 三点求圆心坐标 4. 三角形几个重要的点 七. 专题讨论 1. 树状数组 2. 字典树 3. 后缀树 4. 线段树 5. 查集 6. 二叉 7. 逆序数(排序) 8. 树状DP 9. 欧拉路 10. 八数码 11. 高斯消元法 12. 字符串匹配(KMP算法) 13. 全排列,全组合 14. 二维线段树 15. 稳定婚姻匹配 16. 后缀数组 17. 左偏树 18. 标准RMQ-ST 19. 度限制最小生成树 20. 最优比率生成树(0/1分数规划) 21. 最小花费置换 22. 区间K大数 23. LCA - RMQ-ST 24. LCA – Tarjan 25. 指数型母函数 26. 指数型母函数(大数据) 27. 单词前缀树(字典树+KMP) 28. FFT(大数乘法) 29. 二分图网络最大流最小割 30. 混合图欧拉回路 31. 无源汇上下界网络流 32. 二分图最小点权覆盖 33. 带约束的轨道计数(Burnside引理) 34. 三分法求函数波峰 35. 单词计数,矩阵乘法 36. 字符串和数值hash 37. 滚动队列,前向星表示法 38. 最小点基,最小权点基
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值