首页 > 其他分享 >左偏树

左偏树

时间:2023-02-19 18:12:41浏览次数:43  
标签:val int fa 左偏 节点 dis

参考:bilibili

可并堆

优先队列的缺点:无法高效合并两个堆

左偏树

节点中的数字是它的键值,而它是个堆,因此它满足堆的性质:节点的键值小于左右子节点的键值

节点外蓝色数字叫做该节点的距离

  • 距离的定义:离该节点最近的“外节点”到该节点的距离
  • 外节点:左右儿子不完整的节点

左偏树保证节点的左儿子的距离不小于右儿子的距离

因此可以推出一个节点距离等于右子节点的距离+1

所以空节点的距离为-1

另外,一个N节点的左偏树根节点的距离最大为 \(\log (N+1) -1\)

性质总结

  1. 小根性质:节点键值小于等于左右儿子的键值
  2. 左偏性质:节点左儿子的距离不小于其右儿子的距离
  3. 节点的距离等于其右儿子的距离+1

合并

合并过程中,要维护好左偏树的性质,只要合并后的堆依然满足性质,就合并成功

具体步骤

  1. 设要合并的两个堆的堆顶节点为 x, y,且 \(val_x \le val_y\)
  2. 因为小根性质,合并好后的堆的堆顶一定还是 x,所以我们递归合并 x 的儿子(左右都行,一般用右)和 y
  3. 因为合并完成后可能破坏 x 的左偏性质,所以如果 x 不满足左偏性质了,就交换 x 的左右儿子
  4. x 的距离有可能变化,利用性质三,令 \(dist_x\) 等于其右儿子的距离 +1

push 与 pop

  • push:

和 fhq treap 一样,新建节点然后合并即可

  • pop:

合并堆顶节点的两儿子作为新堆顶即可

另外可以把节点的值设为 -1 来标记这个节点被删除了

所以原堆顶的值被设置为 -1 来标记其被删除了

模板题——P3377

  • 节点
struct node
{
    int l, r, fa;
    int val, dis;
}t[N];
  • 初始化
t[0].dis = -1;
for(int i = 1; i <= n; i ++ )
{
    t[i].val = read();
    t[i].fa = i;
}
  • 合并
int merge(int x, int y)
{
    if(!x || !y) return x + y;
    //||前维护小根堆, ||后是题目要求值相同的下标小的优先级高
    if(t[x].val > t[y].val || (t[x].val == t[y].val && x > y))
        swap(x, y);
    
    t[x].r = merge(t[x].r, y);
    t[t[x].r].fa = x;
    if(t[t[x].l].dis < t[t[x].r].dis)
        swap(t[x].l, t[x].r);
    
    t[x].dis = t[t[x].r].dis + 1;
    return x;
}
  • 并查集
int find(int x)
{
    if(t[x].fa != x) t[x].fa = find(t[x].fa);
    return t[x].fa;
}
  • 删除堆顶
inline void pop(int x)
{
    t[x].val = -1;
    t[t[x].l].fa = t[x].l;
    t[t[x].r].fa = t[x].r;
    t[x].fa = merge(t[x].l, t[x].r);
}
  • 完整代码
#define LOCAL
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

const int N = 1e5 + 10;
struct node
{
    int l, r, fa;
    int dis, val;
}t[N];

int merge(int x, int y)
{
    if(!x || !y) return x + y;

    if(t[x].val > t[y].val || (t[x].val == t[y].val && x > y))
        swap(x, y);
    
    t[x].r = merge(t[x].r, y);
    t[t[x].r].fa = x;
    if(t[t[x].l].dis < t[t[x].r].dis)
        swap(t[x].l, t[x].r);
    
    t[x].dis = t[t[x].r].dis + 1;
    return x;
}

inline void pop(int x)
{
    t[x].val = -1;
    t[t[x].l].fa = t[x].l;
    t[t[x].r].fa = t[x].r;
    t[x].fa = merge(t[x].l, t[x].r);
}

int find(int x)
{
    if(t[x].fa != x) t[x].fa = find(t[x].fa);
    return t[x].fa;
}

int n, m;

int main()
{
    #ifdef LOCAL
        freopen("D:\\workspace\\in_and_out\\in.in", "r", stdin);
        freopen("D:\\workspace\\in_and_out\\out.out", "w", stdout);
    #endif

    n = read(), m = read();
    t[0].dis = -1;

    for(int i = 1; i <= n; i ++ )
    {
        t[i].val = read();
        t[i].fa = i;
    }

    while(m -- )
    {
        int op = read(), x = read();
        if(op == 1)
        {
            int y = read();
            if(t[x].val == -1 || t[y].val == -1) continue;

            int pa = find(x), pb = find(y);
            if(pa == pb) continue;
            t[pa].fa = t[pb].fa = merge(pa, pb);
        }
        else
        {
            if(t[x].val == -1) puts("-1");
            else 
            {
                cout << t[find(x)].val << endl;
                pop(find(x));
            }
        }
    }
    
    return 0;
}

标签:val,int,fa,左偏,节点,dis
From: https://www.cnblogs.com/crimsonawa/p/17135254.html

相关文章

  • 左偏树
    一.定义与性质1.外节点:一棵二叉树中左儿子或右儿子为空的节点称为外节点。2.左偏树(LeftistTree)是一种可并堆的实现。左偏树是一棵二叉树,每个节点维护的值有:左右儿......
  • 左偏树 学习笔记
    左偏树是一类拥有下列操作的数据结构:插入一个数\(O(logn)\)求最小值\(O(1)\)删除一个节点\(O(logn)\)合并两棵树\(O(logn)\)左偏树本身具有堆的性质......
  • [可并堆] 左偏树
    左偏树0x00绪言左偏树是一种比较神奇的数据结构,代码实现类似于线段树,但又是一种原理和线段树完全不一样的数据结构,如果读者打算阅读此博客,一定要读完,不要只看前半部分分......
  • 2714. 左偏树
    题目链接2714.左偏树你需要维护一个小根堆的集合,初始时集合是空的。该集合需要支持如下四种操作:1a,在集合中插入一个新堆,堆中只包含一个数\(a\)。2xy,将第\(x\)......
  • LuoguP3377 【模板】左偏树(可并堆)
    题意如题,一开始有\(n\)个小根堆,每个堆包含且仅包含一个数。接下来需要支持两种操作:1xy:将第\(x\)个数和第\(y\)个数所在的小根堆合并(若第\(x\)或第\(y\)个......