• 左偏树


    左偏树

    定义

    左偏树(英语:leftist treeleftist heap),也可称为左偏堆、左倾堆,是计算机科学中的一种树,是一种优先队列实现方式,属于可并堆,在信息学中十分常见,在统计问题、最值问题、模拟问题和贪心问题等等类型的题目中,左偏树都有着广泛的应用。斜堆是比左偏树更为一般的数据结构。


    ---------------百度百科

    ​ 在这里我们引入一个外节点的概念:左子树或右子树为空的节点。

    ​ 在引入一个距离的概念:一个节点到它最近的外节点所经过的距离。

    性质

    ​ 1.左偏树是一个堆,堆顶元素小于(大于)它左右儿子的元素;

    ​ 2.左偏树左儿子的距离大于等于右儿子的距离;

    ​ 3.节点的距离等于它右儿子的距离+1;

    ​ 4.一颗有\(n\)个节点的左偏树,它的深度不超过\(\lfloor log_{2}(n+1) \rfloor - 1\)

    ​ 证明一下性质4,当左偏树是一颗满二叉树时,它的节点数有\(2^{k + 1} - 1\)个,所以\(n <= 2^{k + 1} - 1\),转化一下,\(k <= \lfloor log_2(n + 1) \rfloor - 1\)

    操作

    合并

    ​ 合并是左偏树最基础的操作,其他操作都要依赖合并操作。

    ​ 先结合图片理解一下合并的过程:

    ​ 我们将两个堆合并,将堆顶元素的大的堆(A)合并在堆顶元素小的堆(B)的下边(根据不同题意),如果A < B,交换就好了。我们每次将B与A的右子树合并,合并完如果发现左子树的距离小于右子树的距离,那么就交换左右子树,然后利用性质3更新B的距离。

    ​ 根据性质4,合并的最差复杂度是\(O(log(sizeA) +log(sizeB))\)

    int merge(int x, int y) {
        if(x == 0 || y == 0) return x + y; //如果有一棵树为空,直接返回那个不为空的树
        if(vis[x] > vis[y] || (vis[x] == vis[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; //性质3
        return x;
    }
    

    插入

    ​ 就是将一个节点与一个堆合并。

    删除

    ​ 找到\(x\)的堆顶,将堆顶的左右子树合并。

    int find(int x) {
    	if(fa[x]) x = fa[x]; return x;
    }
    
    void pop(int x) {
        x = find(x);
        f[ch[x][1]] = f[ch[x][0]] = 0;
        merge(ch[x][1], ch[x][0]);
    }
    

    模板

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

    ​ 这个题注意找堆顶时要写路径压缩,不然最后一个点过不去。因为\(find\)函数是\(O(n)\)的。

    ​ 那怎么写路径压缩呢?路径压缩可能会改变左偏树的性质,我们在删点操作时,把删除的点连在合并完的堆下面,之后路径压缩找父亲时就不会断掉。

    #include <cstdio>
    #define R register
    
    using namespace std;
    
    inline int read() {
    	int s = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
    	while(ch >= '0' && ch <= '9') { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
    	return s * f;
    }
    
    const int N = 1e5 + 5;
    int n, m;
    int vis[N], ch[N][2], f[N], dis[N], fa[N];
    
    inline int find(R int x) {
    	while(f[x] == 0) return x;
    	return f[x] = find(f[x]);
    }
    
    void swap(int &x, int &y) { int t = x; x = y; y = t; }
    
    int merge(R int x, R int y) {
    	if(x == 0 || y == 0) return x + y;
    	if(vis[x] > vis[y] || (vis[x] == vis[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][1], ch[x][0]);
    	dis[x] = dis[ch[x][1]] + 1;
    	return x;
    }
    
    void div(R int x) {
    	vis[x] = -1;
    	f[ch[x][0]] = 0; f[ch[x][1]] = 0;
    	f[x] = merge(ch[x][1], ch[x][0]);
    }
    
    int main() {
    	
    	n = read(); m = read(); dis[0] = -1;
    	for(int i = 1;i <= n; i++) vis[i] = read(), fa[i] = 0;
    	for(int i = 1;i <= m; i++) {
    		int opt = read();
    		if(opt == 1) {
    			int x = read(), y = read();
    			if(vis[x] == -1 || vis[y] == -1 || x == y) continue;
    			x = find(x), y = find(y);
    			x = merge(x, y);
    		}
    		else {
    			int x = read();
    			if(vis[x] == -1) printf("-1\n");
    			else {
    				x = find(x); printf("%d\n", vis[x]); div(x);
    			}
    		}
    	}
    	
    	return 0;
    }
    
  • 相关阅读:
    js用8421码实现10进制转2进制
    什么?toggle(fn1, fn2)函数在1.9版本jq被移除? 来来来,自己撸一个
    js获取鼠标点击的对象,点击另一个按钮删除该对象
    html5小结
    iphone状态栏高度?
    制作手机相册 全屏滚动插件fullpage.js
    js 相关知识整理(一)
    css 居中问题
    进度条
    @Html.Raw()
  • 原文地址:https://www.cnblogs.com/czhui666/p/13395976.html
Copyright © 2020-2023  润新知