左偏树(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所得的结果。

本题为左偏树模板题; 我左偏树的第一题。

左偏树有合并,删除的操作。具体左偏树能做什么题,目前只知道有关合并的题,是可以用左偏树来做的,其他的以后再来补充。

在用左偏树的时候。

具体有三个框架

1.getf 即寻找祖先;

2.Merge 合并操作,如果是最小堆,则要满足x<y,最大堆反过来(这是我刚做这些题的时候的见解,目前认为就是这样)

然后进行合并的递归操作,最后则要满足左偏,即dis【x】>dis【y】;

         为什么要左偏?????   这可能是左偏树最重要的思想了;

        左偏之后,能保证右边的深度较小,别人创造的这一算法里,是往右子树进行合并操作,操作的时间复杂度自然是按右子树的深度来算;

        所以为了保证时间复杂度较小(logn)便要在右子树深度大于左子树时,交换两者的值;

3.pop操作,这个操作,是剔除堆中的最大值或者最小值,然后再将他的左右子树合并,其中一个成为新的根。然后再将被剔除点的父亲定为新根

为什么要定为新根呢,因为可能在下面的点中有直接指向这个点的节点。所以要将这些点指向新的。

#include<cstdio>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
const int maxn=1e5+10;
int val[maxn];
int f[maxn];
int ch[maxn][2];
int dis[maxn];
int getf(int x)  //标准并查集
{
    if(f[x]==x) return x;
    else{
        f[x]=getf(f[x]);
        return f[x];
    }
}
int Merge(int x,int y)
{
    if(!x||!y) 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);
    f[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;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&val[i]);
        f[i]=i;
    }
    while(m--){
        int ope;
        scanf("%d",&ope);
        if(ope==1){
            int t1,t2;
            scanf("%d%d",&t1,&t2);
            if(val[t1]==-1||val[t2]==-1) continue;
            t1=getf(t1);
            t2=getf(t2);
            if(t1==t2) continue;
            Merge(t1,t2);
        }
        else{
            int t;
            scanf("%d",&t);
            if(val[t]==-1){
                printf("-1\n");
                continue;
            }
            t=getf(t);
            printf("%d\n",val[t]);
            //这里是pop的操作,将被T出的点定为-1;
            val[t]=-1;
            //再将原本的根指向新根,让其他原本指向旧根的点能继续指向新根
            f[ch[t][0]]=f[ch[t][1]]=f[t]=Merge(ch[t][0],ch[t][1]);
            //将旧根的左右儿子以及dis清零
            ch[t][0]=ch[t][1]=dis[t]=0;
        }
    }
    return 0;
}

  

原文地址:https://www.cnblogs.com/pangbi/p/11723305.html

时间: 2024-07-31 00:15:04

左偏树(p3377)的相关文章

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

P3377 [模板]左偏树(可并堆) 题目描述 如题,一开始有N个小根堆,每个堆包含且仅包含一个数.接下来需要支持两种操作: 操作1: 1 x y 将第x个数和第y个数所在的小根堆合并(若第x或第y个数已经被删除或第x和第y个数在用一个堆内,则无视此操作) 操作2: 2 x 输出第x个数所在的堆最小数,并将其删除(若第x个数已经被删除,则输出-1并无视删除操作) 输入输出格式 输入格式: 第一行包含两个正整数N.M,分别表示一开始小根堆的个数和接下来操作的个数. 第二行包含N个正整数,其中第i个

luogu 【P3377】 【模板】左偏树

左偏树模板... #include <cstdio> #include <cstring> #include <cstdlib> #include <cmath> #include <algorithm> #include <cctype> #include <iostream> #define For(i, l, r) for(int i = (l); i <= (int)(r); ++i) #define For

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

因为也是昨天刚接触左偏树,从头理解,如有不慎之处,跪请指教. 左偏树: 什 么是(fzy说)左偏树啊? 前置知识: 左偏树中dist:表示到右叶点(就是一直往右下找,最后一个)的距离,特别的,无右节点的为0. 堆:左偏树是个堆. 关于左偏性质:可以帮助堆合并(研究深了我也不懂的,看代码理解) 对于任意的节点,dist[leftson]>=dist[rightson],体现了左偏性质. 同理可得:对于任意右儿子的父亲节点的dist自然等于右儿子的dist+1喽 关于各种操作: merge: 是插入

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

采用了jls的左偏树写法,非常好写... 额外用一个并查集维护集合关系即可,注意路径压缩. #include <bits/stdc++.h> using namespace std; typedef long long LL; #define LOG(...) fprintf (stderr, __VA_ARGS__) #define pb push_back #define mp make_pair #define SZ(x) ((int)(x).size()) #define ALL(x)

关于左偏树的一些东东

大概所有的预备知识这里都有https://baike.baidu.com/item/%E5%B7%A6%E5%81%8F%E6%A0%91/2181887?fr=aladdin 例题1:洛谷 P3377 [模板]左偏树(可并堆) 383通过 1.2K提交 题目提供者HansBug 站长团 标签 难度提高+/省选- 时空限制1s / 128MB 提交 讨论 题解 最新讨论更多讨论 加了路径压缩就WA,路过dal… 左偏树用指针写会MLE吗..… m,n写反了也可以过,数据有… 哪位大神有pbds库

模板:左偏树

如果你知道priority_queue的话,那自然就知道左偏树的目的了. 左偏树的目的和优先队列一致,就是求出当前所在堆中的最大(小)值. 但是我们作为高贵的C++选手,我们为什么还要学习左偏树呢. 当然是因为priority_queue太!慢!了! ———————————————————————————————————— 概念引入: 对于左偏树,我们引入两个概念: 外节点:如果该节点的左子树或右子树为空,那么该节点为外节点. 距离(dis):该节点到达最近的外节点经过的边的个数. 我们同时将优

左偏树(可并堆)

"左偏"树? 左偏树其实是一种可并堆,它可以 \(O(log_2 n)\) 合并两个堆. 那左偏?也就是说他左边肯定有什么东西比右边大-- 别着急,在左偏树上有一个叫距离的东西: 个点的距离,被定义为它子树中离他最近的外节点到这个节点的距离(这与树的深度不同) 其中我们定义一个节点为外节点,当且仅当这个节点的左子树和右子树中的一个是空节点.(注意外节点不是叶子节点) 这幅图中的这三个节点都是外节点. 而左偏树指的就是就是一个节点的左儿子的距离一定大于等于右儿子的距离. 例如下面就是一棵

左偏树教程

最近学了左偏树,学的时候深感网上没有详细教程之苦,所以自己来写一篇(因为是蒟蒻所以可能写的不是很好) 左偏树是什么? 左偏,顾名思义,就是往左倾斜,左偏树既满足堆的性质,又满足左偏的性质 因为它向左倾斜,所以可以有效的减少查询的时间复杂度 先来看看一颗左偏树 这就是一颗左偏树(虽然有点丑) 左偏树有两个重要的值:键值,距离 键值就是点的权值,而每个点的距离值就是它的右儿子的距离值加1 维护左偏这一性质靠的就是距离值,而维护堆的性质靠的就是权值 左偏树至少满足以下几种操作 合并,查询,删除 合并:

浅谈左偏树在OI中的应用

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