模板:左偏树

如果你知道priority_queue的话,那自然就知道左偏树的目的了。

左偏树的目的和优先队列一致,就是求出当前所在堆中的最大(小)值。

但是我们作为高贵的C++选手,我们为什么还要学习左偏树呢。

当然是因为priority_queue太!慢!了!

————————————————————————————————————

概念引入:

对于左偏树,我们引入两个概念:

外节点:如果该节点的左子树或右子树为空,那么该节点为外节点。

距离(dis):该节点到达最近的外节点经过的边的个数。

我们同时将优先队列的定义照搬过来:

键值(val):节点的权值。

同时将优先队列的性质照搬过来:

性质1:节点的键值小于或等于它的左右子节点的键值。

对于左偏树,赋予它一个性质:

性质2:节点的左子节点的距离不小于右子节点的距离。

同时可以推出:

性质3:节点的距离等于它的右子节点的距离加1。(显然)

为了满足性质3,我们规定空节点的dis为-1。

此外还有很多性质,不过都是跟推左偏树复杂度相关的性质,有兴趣可以百度。

——————————————————————

复杂度:

这里直接给出左偏树的复杂度:

合并:O(logN1+logN2)(N1和N2分别为待合并左偏树的节点个数)

插入:O(logn)

删除最小节点:O(logn)

建树:O(n)

删除已知编号节点:O(logn)

————————————————————

基本操作:

以建立大根堆为例,现将前三个操作说明。

合并:

  按照左偏树的定义合并即可。

  设要合并的两个堆的根节点为x,y。

  如果其中一个堆为空堆,则不需合并。

  否则比较它们的val,设x为val较小的堆根节点。

  则x显然是新堆(子堆)的根节点,但是y在哪里我们并不知道。

  为了满足左偏树的性质(不提供证明),我们将x的右子堆和y堆合并,最后按照它们的dis交换一下左右儿子即可。

插入:

  将插入元素看成堆的话,与合并相同。

删除最小节点:

  将根节点删除后,其左右子堆合并即可。

————————————————————————————————

学到这里代码实现不是很难,这里提供板子题目:

洛谷P3377:[模板]左偏树(可并堆):https://www.luogu.org/problemnew/show/3377

代码如下:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cctype>
#include<queue>
using namespace std;
const int N=1e5+3;
inline int read(){
    int X=0,w=0;char ch=0;
    while(!isdigit(ch)){w|=ch==‘-‘;ch=getchar();}
    while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}
int fa[N];
struct tree{
    int l,r;
    int dis,val;
}tr[N];
int merge(int x,int y){//两个堆的根,返回新堆的根
    if(x==0||y==0)return x+y;//其中一个是空堆
    if(tr[x].val>tr[y].val||(tr[x].val==tr[y].val&&x>y))
    swap(x,y);//让x的价值比y的价值小,这样x就在y上面
    tr[x].r=merge(tr[x].r,y);//合并x右树和y
    fa[tr[x].r]=x;
    if(tr[tr[x].l].dis<tr[tr[x].r].dis)
    swap(tr[x].l,tr[x].r);//让右儿子更接近外节点
    tr[x].dis=tr[tr[x].r].dis+1;//根据右儿子dis更新节点dis
    return x;
}
int get(int x){
    while(fa[x])x=fa[x];
    return x;
}
void del(int x){
    tr[x].val=-1;
    fa[tr[x].l]=fa[tr[x].r]=0;
    merge(tr[x].l,tr[x].r);
    return;
}
int main(){
    int n=read();
    int m=read();
    for(int i=1;i<=n;i++)tr[i].val=read();
    for(int i=1;i<=m;i++){
    int c=read();
    if(c==1){
        int x=read();
        int y=read();
        if(tr[x].val==-1||tr[y].val==-1||x==y)continue;
        int nx=get(x);
        int ny=get(y);
        merge(nx,ny);
    }else{
        int x=read();
        if(tr[x].val==-1){
        puts("-1");
        }else{
        int y=get(x);
        printf("%d\n",tr[y].val);
        del(y);
        }
    }
    }
    return 0;
}
时间: 2024-11-10 07:42:19

模板:左偏树的相关文章

模板 - 左偏树 + 并查集

这两个经常混在一起用的样子,封成同一个好了. #include<bits/stdc++.h> using namespace std; typedef long long ll; int solve(); int main() { #ifdef Yinku freopen("Yinku.in","r",stdin); #endif // Yinku solve(); } int n,m; const int MAXN=100005; int tot,v[

算法模板——左偏树(可并堆)

实现的功能——输入1 x,将x加入小根堆中:输入2,输出最小值并去在堆中除掉 1 var 2 i,j,k,l,m,n,head:longint; 3 a,lef,rig,fix:array[0..100000] of longint; 4 function min(x,y:longint):longint;inline; 5 begin 6 if x<y then min:=x else min:=y; 7 end; 8 function max(x,y:longint):longint;inl

[模板]左偏树

可并堆 可以支持合并的堆. /*大根堆*/ struct heap{ int l,r,w; }h[N]; int rt[N];//第i个堆的根的下标 /*合并以x,y为根的堆*/ inline int merge(int x,int y){ int t; //其中一个堆为空 if(!x||!y) return x+y; //使得x,y两个根中x大 if(h[x].w<h[y].w){ int t=x;x=y;y=t; } //保持堆两边的平衡 h[x].r=merge(y,h[x].r); t=

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

【模板】左偏树

左偏树是可合并堆的一种实现方式,可合并堆还有其他实现方式比如斜堆,然而我这种蒟蒻只会写左偏树. 模板里的左偏树为大根堆,支持合并,查询堆顶和弹出堆顶操作,对于已经删除的位置,查询将返回-1,为了确保弹出的正常进行,模板里使用的并查集没有使用路径压缩,因此常数可能会比较大. 1 #include<stdio.h> 2 #define maxn 1000 3 struct node{int ch[2],w,dist;}; 4 int n,op,ori[maxn]; 5 void swp(int &

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

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

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

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

【模板】左偏树(可并堆) 可并堆_并查集

左偏树的树高是 $log(n)$ 级别的,所以在查询祖先的时候是不可以直接顺着左偏树上的父亲查询的. 另开一个并查集,在并查集上进行路径压缩的查询即可. Code: #include <cstdio> #include <algorithm> #include <cstring> #define maxn 100006 #define setIO(s) freopen(s".in","r",stdin) using namespa

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

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