• 可并堆(左偏树)简单学习


    通常我们使用的二叉堆,是用一个数组实现的完全二叉树,对于查询有O(1)复杂度,对于插入、删除有O(logn)复杂度

    而且常数比较小,是一个比较优秀的数据结构


    但是。

    当我们需要合并两个堆的时候,用普通的二叉堆就显得吃力了,时间复杂度达到O(mlogn)

    这个时候可并堆这种数据结构就出现了。而在其中,尤以左偏树代码复杂度和时间复杂度综合得比较优秀


    左偏树

    在介绍左偏树之前,得先引入一个叫距离(d)的概念:
    在左偏树中,一个节点u的距离,等于u到它和叶子节点中最近的左右儿子不满的节点【也就是可插入的节点】的距离。

    然后左偏树是这样定义的:
    左偏树是左儿子距离不小于右儿子距离的二叉堆。

    这样子有什么用呢?
    当插入节点时,只需往右儿子插入,可降低插入的时间复杂度。

    实现

    首先每个节点这样声明:
    struct LT{
    	int v,l,r,d;
    	LT() {l=r=d=0;}
    }e[maxn];



    左偏树的所有操作都是以合并操作为基础的:
    对于根节点为u和v两个左偏树是这样合并的:
    1、如果其中有空节点,直接返回另一个非空结点
    2、选择节点值较小的作为根节点,然后合并根节点的右儿子和另一个节点【merge(root.r,v)】【递归】
    3、维护根节点的距离。我们设空节点的距离为-1,那么根节点的距离=右儿子距离+1

    C++代码:

    int merge(int a,int b){
    	if(!a) return b;
    	if(!b) return a;
    	if(e[b].v<e[a].v) a^=b^=a^=b;
    	e[a].r=merge(e[a].r,b);
    	if(e[e[a].l].d<e[e[a].r].d) swap(e[a].l,e[a].r);
    	if(!e[a].r) e[a].d=0;
    	else e[a].d=e[e[a].r].d+1;
    	return a;
    }

    有了merge操作其他一切操作就都特别简单了:
    insert操作:先将建立一个新节点,再将新节点看做一棵树与原树合并。
    del操作:合并根节点的左右子树

    void insert(int x){
    	int b=++siz;
    	e[b].v=x;
    	if(!rt) rt=b;
    	else rt=merge(rt,b);
    }

    void del(){rt=merge(e[rt].l,e[rt].r);}



    左偏树其实甚至可以当普通的二叉堆使用,只不过常数大一些,由于编程难度不高,但有时用的甚至可以成为替代二叉堆的一个选择。

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

    一道板题
    但用了左偏树+并查集
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int maxn=100005,INF=2000000000;
    
    inline int read(){
    	int out=0,flag=1;char c=getchar();
    	while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();}
    	while(c>=48&&c<=57) {out=out*10+c-48;c=getchar();}
    	return out*flag;
    }
    
    int N,M;
    
    struct LT{
    	int v,l,r,d,f;
    	LT() {l=r=d=f=0;}
    }e[maxn];
    
    int merge(int a,int b){
    	if(!a) return b;
    	if(!b) return a;
    	if(e[a].v>e[b].v||(e[a].v==e[b].v&&a>b)) swap(a,b);
    	e[a].r=merge(e[a].r,b);
    	e[e[a].r].f=a;
    	if(e[e[a].l].d<e[e[a].r].d) swap(e[a].l,e[a].r);
    	e[a].d=e[e[a].r].d+1;
    	return a;
    }
    
    void del(int r){
    	e[e[r].l].f=e[e[r].r].f=0;
    	merge(e[r].l,e[r].r);
    	e[r].v=-1;
    }
    
    int find(int u){
    	if(!e[u].v==-1) return 0;
    	while(e[u].f) u=e[u].f;
    	return u;
    }
    
    int main()
    {
    	e[0].d=-1;
    	N=read();
    	M=read();
    	for(int i=1;i<=N;i++)
    		e[i].v=read();
    	int cmd,a,b,fa,fb;
    	while(M--){
    		cmd=read();
    		if(cmd&1){
    			a=read();
    			b=read();
    			if(e[a].v==-1||e[b].v==-1) continue;
    			fa=find(a);
    			fb=find(b);
    			if(fa!=fb) merge(fa,fb);
    		}else{
    			a=read();
    			if(e[a].v==-1) printf("-1
    ");
    			else{
    				fa=find(a);
    				printf("%d
    ",e[fa].v);
    				del(fa);
    			}
    		}
    	}
    	return 0;
    }
    

    洛谷P1456 Monkey King

    差不多的题,左偏树+并查集
    【猴子找朋友来帮忙自己却不打= =】

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int maxn=100005,INF=2000000000;
    
    inline int read(){
    	int out=0,flag=1;char c=getchar();
    	while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();}
    	while(c>=48&&c<=57) {out=out*10+c-48;c=getchar();}
    	return out*flag;
    }
    
    int N,M;
    
    struct LT{
    	int v,l,r,d;
    	LT() {l=r=d=0;}
    }e[maxn];
    int fa[maxn];
    
    int find(int x){return fa[x]==x ? x:fa[x]=find(fa[x]);}
    
    int merge(int u,int v){
    	if(!u) return v;
    	if(!v) return u;
    	if(e[u].v<e[v].v) u^=v^=u^=v;
    	e[u].r=merge(e[u].r,v);
    	fa[e[u].r]=u;
    	if(e[e[u].l].d<e[e[u].r].d) swap(e[u].l,e[u].r);
    	e[u].d=e[e[u].r].d+1;
    	return u;
    }
    
    int del(int u){
    	int lc=e[u].l,lr=e[u].r;
    	e[u].l=e[u].r=0;
    	fa[lc]=lc;fa[lr]=lr;
    	return merge(lc,lr);
    }
    
    int main()
    {
    	e[0].d=-1;
    	int a,b,Fa,Fb;
    	while(cin>>N){
    		for(int i=1;i<=N;i++) {e[i].v=read();e[i].l=e[i].r=e[i].d=0;fa[i]=i;}
    		M=read();
    		while(M--){
    			a=read();
    			b=read();
    			Fa=find(a);
    			Fb=find(b);
    			if(Fa!=Fb){
    				a=del(Fa);
    				b=del(Fb);
    				e[Fa].v>>=1;
    				e[Fb].v>>=1;
    				a=merge(a,Fa);
    				b=merge(b,Fb);
    				a=merge(a,b);
    				printf("%d
    ",e[a].v);
    			
    			}else printf("-1
    ");
    		}
    	}
    	return 0;
    }
    

    洛谷 P1552 [APIO2012]派遣

    这是一道左偏树+树上DP
    用大根堆维护,将所有的儿子【包括儿子的子树】建立成堆,删去最大的直至工资在预期范围内,就可以得到以该点为管理者的最大收益。
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define LL long long int
    using namespace std;
    const int maxn=100005,INF=2000000000;
    
    inline int read(){
    	int out=0,flag=1;char c=getchar();
    	while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();}
    	while(c>=48&&c<=57) {out=out*10+c-48;c=getchar();}
    	return out*flag;
    }
    
    int N,M,lson[maxn],rb[maxn],root;
    LL dp[maxn],lead[maxn];
    
    struct LT{
    	LL v;
    	int l,r,d;
    	LT() {l=r=d=0;}
    }e[maxn];
    
    struct node{
    	LL v;
    	int cnt,rt;
    };
    
    int merge(int u,int v){
    	if(!u) return v;
    	if(!v) return u;
    	if(e[u].v<e[v].v) u^=v^=u^=v;
    	e[u].r=merge(e[u].r,v);
    	if(e[e[u].l].d<e[e[u].r].d) swap(e[u].l,e[u].r);
    	e[u].d=e[e[u].r].d+1;
    	return u;
    }
    
    int del(int u) {return merge(e[u].l,e[u].r);}
    
    node dfs(int u){
    	if(!lson[u]){
    		dp[u]=e[u].v<=M ? lead[u]:0;
    		return e[u].v<=M ? (node){e[u].v,1,u}:(node){0,0,0};
    	}else{
    		int rt=u,cnt=1,v=lson[u];
    		LL s=e[u].v;
    		node t;
    		while(v){
    			t=dfs(v);
    			cnt+=t.cnt;s+=t.v;rt=merge(rt,t.rt);
    			v=rb[v];
    		}
    		while(s>M){
    			s-=e[rt].v;
    			rt=del(rt);
    			cnt--;
    		}
    		dp[u]=(LL)lead[u]*(LL)cnt;
    		return (node){s,cnt,rt};
    	}
    }
    
    int main()
    {
    	e[0].d=-1;
    	int f;
    	N=read();
    	M=read();
    	for(int i=1;i<=N;i++){
    		f=read();rb[i]=lson[f];lson[f]=i;
    		if(!f) root=i;
    		e[i].v=read();
    		lead[i]=read();
    	}
    	dfs(root);
    	LL ans=0;
    	for(int i=1;i<=N;i++) ans=max(ans,dp[i]);
    	cout<<ans<<endl;
    	return 0;
    }
    



  • 相关阅读:
    .Net -- NLog日志框架配置与使用
    Prism -- 简介
    Prism.WPF -- Prism框架使用(下)
    Prism.WPF -- Prism框架使用(上)
    Win32Api -- 关闭当前应用
    WPF -- 一种圆形识别方案
    C#语言特性及发展史
    WPF -- 一种实现本地化的方法
    DEV中右键菜单如何只在非空单元格上显示?
    打开一个窗体,其他窗体处于不可编辑状态
  • 原文地址:https://www.cnblogs.com/Mychael/p/8282896.html
Copyright © 2020-2023  润新知