学习左偏树有感//2018/7/1

大佬们请先对并查集略知一二。

今日,学习了传说中的左偏树(虽然感觉学的还是假的)

学习地址:https://www.luogu.org/blog/IAmHellWord/solution-p3377

附加丑代码一段

#include <bits/stdc++.h>
using namespace std;

#define C getchar()
#define maxn 101000

inline int read()
{
    int x=0;
    char ch;
    bool flag=true;
    while(!isdigit(ch))
    {
        if(ch=='-')
          flag=false;
        ch=C;
    }
    while(isdigit(ch))
    {
        x=(x<<3)+(x<<1)+(ch^48);
        ch=C;
    }
    return flag?x:-x;
}

struct lefttree
{
    int lson;
    int rson;
    int key;
    int fa;
    int dis;
}tree[maxn]={};
int n,m;

int Un(int u,int v){
    if (!u || !v)return u+v;
    if (tree[u].key>tree[v].key || (tree[u].key==tree[v].key && u>v))swap(u,v);
    int &ul=tree[u].lson,&ur=tree[u].rson;
    ur=Un(ur,v);
    tree[ur].fa=u;
    if (tree[ul].dis<tree[ur].dis)swap(ul,ur);
    tree[u].dis=tree[ur].dis+1;
    return u;
}

void pop(int n)
{
    int ul=tree[n].lson,ur=tree[n].rson;
    tree[n].key=-1,tree[ul].fa=0,tree[ur].fa=0;
    Un(ul,ur);
}

int gf(int x)
{
    return tree[x].fa?gf(tree[x].fa):x;
}

void intt()
{
    n=read();
    m=read();
    tree[0].dis=-1;
}

void work()
{
    for(int i=1;i<=n;i++)
      tree[i].key=read();
    for(int i=1;i<=m;i++)
    {
        int opt,x,y;
        opt=read();
        x=read();
        if(opt==1)
        {
            y=read();
            if(tree[x].key!=-1&&tree[y].key!=-1)
            {
                int p=gf(x);
                int q=gf(y);
                if(q!=p)
                  Un(p,q);
            }
        }
        else 
        {
            if(tree[x].key==-1)
               printf("%d\n",-1);
            else 
            {
                int p=gf(x);
                printf("%d\n",tree[p].key);
                pop(p);
            }
        }
    }
}

int main()
{
    intt();
    work();
    return 0;
}

以前仗着有stl库中的堆,打死不学左偏树(反正左偏树除了支持合并以外好像与堆并没有差别),直到碰到了

https://www.luogu.org/problemnew/show/P3377

整个人都不好了。

然后就只有找资料认真学习了,but,为神马网上的blog用的都是指针呢,咱老师说了,用指针的同学特别容易翻车,所以本人就毅然决然地放弃了指针的做法,找到了这么一篇良心题解。

下面就请允许我浅谈一下对左偏树的理解

左偏树,又称可并二叉堆,是基于合并操作的一种数据结构

好了,那么左偏树为什么叫左偏树呢,因为他看起来是向左偏的(我也不知道为什么不规定叫右偏树)

因为他的这一个基本性质,所以也就具备了一下三种特性(以小根堆为例)

1.爸爸节点的权值一定小于等于其左右儿子的权值。

2.左儿子的dis(距离)大于右儿子的dis(dis是此节点到最近叶子结点的距离,即要经过的边的条数)。

3.爸爸节点的dis等于右儿子的dis+1。(废话)

然后根据三个结论,很容易就可以推出一个引理

一个节点个数为n的左偏书的最大距离为log(n+1)-1。

以上便是左偏树的一些基本性质

下面就来讲一些基本操作

第一:合并(代码详见上文自定义函数Un)

这是左偏树操作中最为重要的一个。

他的具体方法为将两棵树一深度较大的那一颗为母树,再将另一棵树合并到母树的右儿子上,然后检查左偏性质,如不满足,交换左右子树,强行使其满足左偏性质。此时由于右儿子可能改变,所以爸爸的深度要改为右儿子的深度+1.

第二:删除最小数(代码详见自定义函数pop)

删除最小数其实就相当于将左右儿子合并,这一个操作是等价于删除爸爸节点的。

第三:查找最小数(代码详见gf)

这里便用到了并查集的思想,由于左偏树的性质一,所以根节点的权值便是最小数。

以上三个蛇皮操作都显示出了左偏树一合并为核心操作的性质,也显示出了左偏树对并查集的依赖性。

本文就这么草草地结束了,欢迎各位dalao在评论区留言指出错误之处或改进建议

转载请标明出处(我相信不会有人转载的)

点赞