zoukankan      html  css  js  c++  java
  • [2018.12.6]左偏树

    其实NOIp之前就学会了...结果咕到了现在...

    我们都知道堆。但是很少有人会手写堆。因为我们有STL,而且手写堆码量不小(据说是吧?没写过)。

    而且堆的(Merge)操作又慢又麻烦。

    于是就有了可并堆。

    即使c++也有自带的可并堆

    左偏树就是其中之一。

    Luogu模板题链接

    什么是左偏树

    就是一颗向左偏的树。(逃

    看一个例子:

    图炸了QWQ

    (来源:百度图片)

    好像确实左偏...

    左偏树中,定义一个外节点为:左孩子或者右孩子为空的节点。

    而一个节点的距离为它的子树中与它最近的外节点的距离。

    外节点的距离为0,空节点的距离为-1(方便计算)。

    距离在图中用蓝色字体标出。

    接下来的内容中,我们维护大根堆(小根堆类似),对于左偏树的一个节点,其定义如下:

    struct Ltree{//丑陋的结构体名
    	int v,d,f,c[2];
        //v:节点的值 d:距离 f:父亲 c[0]:左孩子 c[1]:右孩子
    }t[Size];//丑陋的变量名
    

    左偏树的性质

    左偏性质(不然干嘛叫左偏树):

    对于任意节点(x),有(t_{t_x.c_0}.dge t_{t_x.c_1}.d),说人话就是一个节点的左孩子的距离大于它右孩子的距离。

    这个性质保证左偏树的时间复杂度(介绍合并操作的时候会让大家感性理解这一点)。

    堆性质(不然干嘛叫可并堆):

    对于任意节点(x),有(t_x.vge t_{t_x.c_0}.v),(t_x.vge t_{t_x.c_1}.v),说人话就是任何一个节点的值大于等于它所有孩子的值。

    这个性质保证左偏树的正确性。

    左偏树的操作

    注意:如果像模板题中一样需要判断是否被删除,需要另行记录。

    找根操作((Find(x)))

    找到(x)节点的根。

    不停跳(t_x.f)即可。

    code:

    int Find(int x){
        while(t[x].f)x=t[x].f;
        return x;
    }
    

    合并操作((Merge(x,y)))

    将以(x,y)为根的堆并在一起。

    由于要维护堆性质,合并它们时,要让值大的在上面。

    我们假设(x)为根,那么如果(t_x.v<t_y.v),就交换(x)(y)

    然后呢?不用管然后了,直接递归到(Merge(t_x.c_1,y))即可。

    注意这里我们把(y)(t_x.c_1)合并,而不是(t_x.c_0),就是因为左偏性质,右边的距离更小,在右边进行合并可以维持它的平衡,确保复杂度。(你要我严谨证明我也不会啊...)

    然后此时(t_x.c_0)的距离有可能小于(t_x.c_1),如果出现这种情况,交换(x)的左右儿子。

    然后需要更新(x)的距离。

    显然(t_x.d=t_{t_x.c_1}.d+1)。应该很容易理解。

    code:

    void Merge(int &x,int &y){
    	if(!y)return;//结束
    	if(!x){//结束
    		swap(x,y);
    		return;
    	}
    	if(t[x].v<t[y].v)swap(x,y);//维护堆性质,交换x,y
    	Merge(t[x].c[1],y);
    	t[t[x].c[1]].f=x;//更新右孩子的父亲
    	if(t[t[x].c[0]].d<t[t[x].c[1]].d)swap(t[x].c[0],t[x].c[1]);//维护左偏性质,交换x的左右孩子
    	t[x].d=t[t[x].c[1]].d+1;//更新距离
    }
    

    删除操作((Delete(x)))

    删除节点(x)所在堆的最大值。

    找到(x)所在左偏树的根,将根的左右孩子的父亲设为空,合并它们即可。

    code:

    void Delete(int x){
    	t[t[x].c[0]].f=t[t[x].c[1]].f=0;
    	Merge(t[x].c[0],t[x].c[1]);
    }
    
    Upd(2018.12.12)

    才知道删除还可以删除任意已知节点。。。

    这里的已知指我们可以直接访问它的位置,而不是知道它的值。比如左偏树不能完成例如删除树中值为233的节点,但是可以进行例如删除编号为233的节点(我们可以直接访问233号节点)。

    类似删除根的方法,我们先合并它的左右子树,然后一直跳它的父亲:

    当他父亲的距离等于新合并的子树的距离+1,就可以停止了;

    当它的父亲的距离大于新合并的子树的距离+1,更新父亲的距离,如果这棵子树是父亲的左子树的话还需要交换父亲的子树;

    当它的父亲的距离小于新合并的子树的距离+1,如果这棵子树是父亲的左子树就停止,否则更新父亲的距离为两棵子树距离的较小值+1,如果左子树的距离更小就交换左子树。

    因为作者很懒所以代码不给出。

    最值操作((Max(x)))

    返回(x)所在左偏树的最大值。

    也就是返回根的值啦!

    code:

    int Max(int x){
    	return t[Find(x)].v;
    }
    

    没了。

    这个数据结构是如此的简单。

    还有一点要说的:关于给一个序列建左偏树。

    给每个元素建一棵一个节点的左偏树,顺序合并即可。时间复杂度(O(nlogn))

    讲完了。

    最后附上模板题代码:

    #include<bits/stdc++.h>
    using namespace std;
    struct Pair{
        int v,id;
        bool operator >(Pair y){
            if(v!=y.v)return v>y.v;
            return id>y.id;
        }
        bool operator <(Pair y){
            if(v!=y.v)return v<y.v;
            return id<y.id;
        }
    };
    struct node{
        int f,d,c[2];
        Pair v;
    }t[100010];
    int n,m,op,u,v,fu,fv,del[100010];
    int getf(int x){
        while(t[x].f)x=t[x].f;
        return x;
    }
    void Merge(int &x,int &y){
        if(!y)return;
        if(!x){
            swap(x,y);
            return;
        }
        if(t[x].v>t[y].v){
            swap(x,y);
        }
        Merge(t[x].c[1],y);
        t[t[x].c[1]].f=x;
        if(t[t[x].c[1]].d>t[t[x].c[0]].d){
            swap(t[x].c[1],t[x].c[0]);
        }
        t[x].d=t[t[x].c[1]].d+1;
    }
    int Delete(int x){
        t[t[x].c[0]].f=t[t[x].c[1]].f=0;
        Merge(t[x].c[0],t[x].c[1]);
        del[x]=1;
        return t[x].v.v;
    }
    int main(){
        scanf("%d%d",&n,&m);
        t[0].d=-1;
        for(int i=1;i<=n;i++){
            scanf("%d",&t[i].v.v);
            t[i].v.id=i;
            t[i].d=0;
        }
        for(int i=1;i<=m;i++){
            scanf("%d",&op);
            if(op==1){
                scanf("%d%d",&u,&v);
                fu=getf(u);
                fv=getf(v);
                if(del[u]||del[v]||fu==fv)continue;
                Merge(fu,fv);
            }else{
                scanf("%d",&u);
                if(del[u]){
                    puts("-1");
                }else{
                    fu=getf(u);
                    printf("%d
    ",Delete(fu));
                }
            }
        }
        return 0;
    }
    

    练习题:

    [APIO2012]派遣

    ->Luogu

    ->BZOJ

    ->题解

  • 相关阅读:
    PIE-Basic 自定义滤波
    PIE-Basic 常用滤波
    PIE-Basic 中值滤波
    PIE-Basic 均值滤波
    PIE-Basic 小波变换
    PIE-Basic 傅里叶变换
    PIE-Basic 去相关拉伸
    .net调用数据库执行Mysql存储过程,提示 Procedure or function XXXX Cannot be found in database xxx
    C#几种类型转换方法的个人总结
    Winform无法改变窗体大小
  • 原文地址:https://www.cnblogs.com/xryjr233/p/10536872.html
Copyright © 2011-2022 走看看