• [note]左偏树(可并堆)


    左偏树(可并堆)https://www.luogu.org/problemnew/show/P3377

    题目描述

    一开始有N个小根堆,每个堆包含且仅包含一个数。接下来需要支持两种操作:
    操作1: 1 x y 将第x个数和第y个数所在的小根堆合并(若第x或第y个数已经被删除或第x和第y个数在用一个堆内,则无视此操作)
    操作2: 2 x 输出第x个数所在的堆最小数,并将其删除(若第x个数已经被删除,则输出-1并无视删除操作)

    输入格式:

    第一行包含两个正整数N、M,分别表示一开始小根堆的个数和接下来操作的个数。
    第二行包含N个正整数,其中第i个正整数表示第i个小根堆初始时包含且仅包含的数。
    接下来M行每行2个或3个正整数,表示一条操作,格式如下:
    操作1 : 1 x y
    操作2 : 2 x

    输出格式:

    输出包含若干行整数,分别依次对应每一个操作2所得的结果。

    输入样例:

    5 5
    1 5 4 2 3
    1 1 5
    1 2 5
    2 2
    1 4 2
    2 2

    输出样例:

    1
    2

    说明

    当堆里有多个最小值时,优先删除原序列的靠前的,否则会影响后续操作1导致WA。
    时空限制:1000ms,128M

    数据规模:

    对于30%的数据:N<=10,M<=10
    对于70%的数据:N<=1000,M<=1000
    对于100%的数据:N<=100000,M<=100000

    样例说明:

    初始状态下,五个小根堆分别为:{1}、{5}、{4}、{2}、{3}。
    第一次操作,将第1个数所在的小根堆与第5个数所在的小根堆合并,故变为四个小根堆:{1,3}、{5}、{4}、{2}。
    第二次操作,将第2个数所在的小根堆与第5个数所在的小根堆合并,故变为三个小根堆:{1,3,5}、{4}、{2}。
    第三次操作,将第2个数所在的小根堆的最小值输出并删除,故输出1,第一个数被删除,三个小根堆为:{3,5}、{4}、{2}。
    第四次操作,将第4个数所在的小根堆与第2个数所在的小根堆合并,故变为两个小根堆:{2,3,5}、{4}。
    第五次操作,将第2个数所在的小根堆的最小值输出并删除,故输出2,第四个数被删除,两个小根堆为:{3,5}、{4}。
    故输出依次为1、2。

    给大家介绍一篇左偏树讲解

    以下摘自国集2005论文集

    左偏树(Leftist Tree)
    是一种可并堆的实现。左偏树是一棵二叉树,它的节点除了和二叉树的节点一样具有左右子树指针外,还有两个属性:点权(键值)和距离(dist).
    左偏树满足下面两条基本性质:
    [性质1] 节点的键值小于或等于它的左右子节点的键值.
    这条性质又叫堆性质。符合该性质的树是堆有序的。
    有了性质1,我们可以知道左偏树的根节点是整棵树的最小节点,于是我们可以在O(1) 的时间内完成取最小节点操作。
    
    [性质2] 节点的左子节点的距离不小于右子节点的距离.
    即dist(left(i))≥dist(right(i))  这条性质称为左偏性质。
    性质2是为了使我们可以以更小的代价在优先队列的其它两个基本操作(插入节点、删除最小节点)进行后维持堆性质。
    这两条性质是对每一个节点而言的,因此可以简单地从中得出,左偏树的左右子树都是左偏树。
    由这两条性质,我们可以得出左偏树的定义:左偏树是具有左偏性质的堆有序二叉树。
    
    习惯上常用根节点序号表示左偏树
    
    #define RG register
    #include<cstdio>
    #include<iostream>
    using namespace std;
    const int N=1e5+5;
    inline int read()
    {
    	RG int x=0,w=1;RG char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    	return x*w;
    }
    int n,m;
    int num[N],ls[N],rs[N],root[N],dist[N];//num[]为每个点的值,ls[]和rs[]为左右儿子,root[]表示该节点所在堆的堆顶编号(左偏树根节点),dist[i]表示节点i节点i到它的后代中最近的外节点所经过的边数[某节点称为外节点,当且仅当该节点的左子树或右子树为空].
    bool del[N];//判断点是否被删除
    int find(int x){return x==root[x]?x:root[x]=find(root[x]);}//并查集找根节点
    int Merge(int a,int b)
    {
    	if(!a||!b)return a+b;//若某一子树为空,返回另一棵子树
    	if(num[a]>num[b]||(num[a]==num[b]&&a>b))swap(a,b);//维护小根堆[权值相同,序号优先]
    	rs[a]=Merge(rs[a],b);//每次将a的右子树与b合并
    	if(dist[ls[a]]<dist[rs[b]])swap(ls[a],rs[a]);//交换左右儿子[保证左偏]
    	dist[a]=dist[rs[a]]+1;
    	return a;
    }
    int main()
    {
    	n=read();
    	m=read();
    	for(int i=1;i<=n;i++)num[i]=read(),root[i]=i;
    	while(m--)
    	{
    		int f=read();
    		if(f==1)
    		{
    			int a=read(),b=read();
    			int x=find(a),y=find(b);
    			if(x==y||del[a]||del[b])continue;
    			root[x]=root[y]=Merge(x,y);//修改根节点
    		}
    		else
    		{
    			int x=read();
    			if(del[x]){printf("-1
    ");continue;}
    			x=find(x);
    			printf("%d
    ",num[x]);
    			del[x]=true;
    			root[x]=Merge(ls[x],rs[x]);//修改根节点
    			if(ls[x]==root[x])root[ls[x]]=ls[x];//将根节点的根置为自己[防止find()递归出错]
    			if(rs[x]==root[x])root[rs[x]]=rs[x];//将根节点的根置为自己
    			ls[x]=rs[x]=0;//清空被删除点的左右儿子
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    Java 过滤器
    理解Java中的弱引用(Weak Reference)
    AOP编程
    利用ThreadLocal管理事务
    Redis设计与实现-附加功能
    Redis设计与实现-主从、哨兵与集群
    Redis设计与实现-客户端服务端与事件
    Redis设计与实现-持久化篇
    Redis设计与实现-内部数据结构篇
    重温软件架构设计-程序员向架构师转型必备
  • 原文地址:https://www.cnblogs.com/sdzwyq/p/8460195.html
Copyright © 2020-2023  润新知