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

因为也是昨天刚接触左偏树,从头理解,如有不慎之处,跪请指教。

左偏树:

什 么是(fzy说)左偏树啊?

前置知识:

  左偏树中dist:表示到右叶点(就是一直往右下找,最后一个)的距离,特别的,无右节点的为0。

  堆:左偏树是个堆。

  关于左偏性质:可以帮助堆合并(研究深了我也不懂的,看代码理解)

  对于任意的节点,dist[leftson]>=dist[rightson],体现了左偏性质。

  同理可得:对于任意右儿子的父亲节点的dist自然等于右儿子的dist+1喽

关于各种操作:

merge:

  是插入操作的函数,具体步骤如下:

  1.对于两个堆x,y,判断x,y是否为0,如果有一个为0,相当于没合并,直接返回另一个有元素的堆。

  2.找到value值更大的那个堆头放到顶上,如果value值一样的话就按堆顶编号来排序。为了方便代码实现,我们可以规定x为符合条件(小,大跟堆)的那个堆头,然后如果y符合条件就交换x,y值。

  3.既然堆头找着了,就可以进一步的合并堆头右儿子和y堆了(为了尽量保证左偏的性质)。如此递归下去,随着新堆头被一次次确定,最终这个堆会被一点一点融合到另一个堆中。

  4.但是,鉴于合成完后,不一定能够保证左子树的dist值一定会比右字数的大,我们只要判断一下是否符合左偏性质,如果不符合,就交换一下当前节点左右子树就行了。因为是递归执行,从更深节点一层一层上来,那么必然的整个堆会符合左偏性质。然后更新一下dist为右子树dist+1.一次merge完成。

代码:

inline int merge(int x, int y)
{
    if(!x||!y)return x+y;
    if(tree[x].value>tree[y].value||(tree[x].value==tree[y].value&&x>y))swap(x,y);
    rs=merge(rs,y);
    if(tree[ls].dist<tree[rs].dist)swap(ls, rs);tree[ls].rt=tree[rs].rt=tree[x].rt=x,tree[x].dist=tree[rs].dist+1;
    //更新dist
     return x ;
}

2.pop弹出函数:

弹出函数,即弹出堆顶。方法很简单:没有了堆顶,整个左偏树就被分为了两个小的左偏树。我们只要忽略掉堆顶合并(merge)两个小的左偏树即可。

注意事项:不要忘了堆顶元素相关信息还原为初始。

代码:

inline void pop(int x)//弹出x为堆顶的堆
{
    tree[x].value=-1,tree[ls].rt=ls,tree[rs].rt=rs;
    tree[x].rt=merge(ls,rs);
} 

3.get函数:

没啥可说的,就是并查集找父亲并且路径压缩。

代码:

int get(int x)
{
    return x==tree[x].rt?x:tree[x].rt=get(tree[x].rt);
} 

三个函数代码已经完结。

main函数内根据题意进行模拟即可。

总代码:

#include<queue>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#define N 100003
#define ls tree[x].son[0]
#define rs tree[x].son[1]
using namespace std;
int read()
{
    int ans=0;
    char ch=getchar(),last=‘ ‘;
    while(ch<‘0‘||ch>‘9‘)last=ch,ch=getchar();
    while(ch>=‘0‘&&ch<=‘9‘)ans=(ans<<3)+(ans<<1)+ch-‘0‘,ch=getchar();
    return last==‘-‘?-ans:ans;
}
inline void swap(int &x,int &y)
{
    x^=y^=x^=y;
}
int n,num,hea[N],t,judge,b,c;
struct tre{
    int son[2],rt,dist,value;
}tree[N];
inline int merge(int x, int y)
{
    if(!x||!y)return x+y;
    if(tree[x].value>tree[y].value||(tree[x].value==tree[y].value&&x>y))swap(x,y);
    rs=merge(rs,y);
    if(tree[ls].dist<tree[rs].dist)swap(ls, rs);tree[ls].rt=tree[rs].rt=tree[x].rt=x,tree[x].dist=tree[rs].dist+1;
    //更新dist
     return x ;
}
int get(int x)
{
    return x==tree[x].rt?x:tree[x].rt=get(tree[x].rt);
}
inline void pop(int x)//弹出x为堆顶的堆
{
    tree[x].value=-1,tree[ls].rt=ls,tree[rs].rt=rs;
    tree[x].rt=merge(ls,rs);
}
int main(){
    n=read(),t=read();tree[0].dist=-1;
    for (int i=1;i<=n;i++)
        tree[i].rt=i,scanf("%d",&tree[i].value);//并差集初始化+输入
    for (int i=1;i<=t;i++){
        judge=read(),b=read();
        if (judge==1){
            c=read();
            if (tree[b].value==-1||tree[c].value==-1) continue ;
            int f1=get(b),f2=get(c);if(f1!=f2)tree[f1].rt=tree[f2].rt=merge(f1,f2);//合并操作
        }
        else {
            if(tree[b].value==-1)printf("-1\n") ;
            else printf("%d\n",tree[get(b)].value),pop(get(b)) ;//输出并弹出
        }
    }
    return 0 ;
}

完结。

  

原文地址:https://www.cnblogs.com/lbssxz/p/11748081.html

时间: 2025-01-10 08:49:02

P3377 【模板】左偏树(可并堆) 左偏树浅谈的相关文章

浅谈Java中的栈和堆

人们常说堆栈堆栈,堆和栈是内存中两处不一样的地方,什么样的数据存在栈,又是什么样的数据存在堆中? 这里浅谈Java中的栈和堆 首先,将结论写在前面,后面再用例子加以验证. Java的栈中存储以下类型数据,栈对应的英文单词是Stack 基本类型 引用类型变量 方法 Java的堆中存储以下类型数据,堆对应的英文单词是Heap 实例对象 在函数中定义的一些基本类型的变量(8种)和对象的引用变量都是在函数的栈Stack内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当

可并堆之左偏树浅谈

左偏树是用来快速地合并堆的 正常的堆是一颗完全二叉树,我们用笨方法去合并它: 假设我们要将x和y这两个小根堆合并,我们判断一下如果x的堆顶大于y的堆顶,就交换一下x和y,然后继续合并x的某个子孩子和y. 堆被人们所推广的原因就是因为它的时间复杂度比较稳定,根本原因是堆是一颗完全二叉树 但显然的:这样合并堆并没有保证时间复杂度,也就是说没有维护完全二叉树的形态: 这时候解决的办法之一便是利用左偏树: 它比普通的堆多了一个性质:向左偏: 注意,这里的向左偏并不是指子树的大小向左偏,而是最大深度向左偏

浅谈左偏树在OI中的应用

Preface 可并堆,一个听起来很NB的数据结构,实际上比一般的堆就多了一个合并的操作. 考虑一般的堆合并时,当我们合并时只能暴力把一个堆里的元素一个一个插入另一个堆里,这样复杂度将达到\(\log(|A|)+\log(|B|)\),极限数据下显然是要T爆的. 所以我们考虑使用一种性价比最高的可并堆--左偏树,它的思想以及代码都挺简单而且效率也不错. 学习和参考自这里 What is Leftist Tree 左偏树,顾名思义就是像左偏的树,但是这样抽象的表述肯定是不符合我们学OI的人的背板子

浅谈左偏树

可并堆 可并堆顾名思义就是可以合并的堆. 这里不讲二项堆和斐波那契堆,只讲左偏树. 左偏树 左偏树顾名思义就是向左偏的树. 给每个点定义一个\(dist\),满足下面三个条件: 1.空结点的\(dist\)等于\(-1\) 2.每个结点的左儿子的\(dist\)都大于右儿子的\(dist\) 3.每个结点的\(dist\)都等于右儿子的\(dist+1\) 根据上面这些性质,我们可以推出左偏树中根结点的\(dist\)最大不超过\(logsize\). 合并 左偏树合并非常简单,假设我要合并\(

笔试算法题(46):简介 - 二叉堆 &amp; 二项树 &amp; 二项堆 &amp; 斐波那契堆

二叉堆(Binary Heap) 二叉堆是完全二叉树(或者近似完全二叉树):其满足堆的特性:父节点的值>=(<=)任何一个子节点的键值,并且每个左子树或者右子树都是一 个二叉堆(最小堆或者最大堆):一般使用数组构建二叉堆,对于array[i]而言,其左子节点为array[2*i],其右子节点为 array[2*i+1]:二叉堆支持插入,删除,查找最大(最小)键值的操作,但是合并二叉堆的复杂度较高,时间复杂度为O(N):但是二项堆或者斐波 那契堆则仅需要O(logN): 二项树(Binomial

poj 1195 二维树状数组 及二维树状数组模板

http://poj.org/problem?id=1195 求矩阵和的时候,下标弄错WA了一次... 求矩形(x1,y1) (x2,y2)的sum |sum=sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1) 二维树状数组讲解:http://blog.csdn.net/u011026968/article/details/38532117 二维树状数组模板: /*========================================

C++:浅谈c++资源管理以及对[STL]智能指针auto_ptr源码分析,左值与右值

C++:浅谈c++资源管理以及对[STL]智能指针auto_ptr源码分析 by 小威威 1. 知识引入 在C++编程中,动态分配的内存在使用完毕之后一般都要delete(释放),否则就会造成内存泄漏,导致不必要的后果.虽然大多数初学者都会有这样的意识,但是有些却不以为意.我曾问我的同学关于动态内存的分配与释放,他的回答是:"只要保证new和delete成对出现就行了.如果在构造函数中new(动态分配内存),那么在析构函数中delete(释放)就可以避免内存泄漏了!" 事实果真如此么?

树套树浅谈

今天来说一下线段树套Splay.顺便我也来重新敲一遍模板. 首先,明确一下Splay套线段树用来处理什么问题.它可以支持:插入x,删除x,单点修改,查询x在区间[l,r]的排名,查询区间[l,r]中排名为k的数,以及一个数在区间[l,r]中的前驱,后继.(应该还可以查询区间和等东西,还没写过) 其实它的常数非常大,但是这是树套树中最容易理解的一种. 首先,我们知道,对于一个区间,我们给它建一棵线段树,它的每个节点维护的是区间[l,r]的信息.所以,我们对于每一个节点都建一棵Splay.(听起来就

浅谈二维中的树状数组与线段树

一般来说,树状数组可以实现的东西线段树均可胜任,实际应用中也是如此.但是在二维中,线段树的操作变得太过复杂,更新子矩阵时第一维的lazy标记更是麻烦到不行. 但是树状数组在某些询问中又无法胜任,如最值等不符合区间减法的询问.此时就需要根据线段树与树状数组的优缺点来选择了. 做一下基本操作的对比,如下图. 因为线段树为自上向下更新,从而可以使用lazy标记使得矩阵的更新变的高校起来,几个不足就是代码长,代码长和代码长. 对于将将矩阵内元素变为某个值,因为树状数组自下向上更新,且要满足区间加法等限制