zoukankan      html  css  js  c++  java
  • 左偏树 学习笔记

    左偏树

    左偏树到底是什么呢??? 左偏树实际上是可合并的堆。

    他的节点不仅存了他的权值,还存了一个比较重要的信息 \(dis\)

    dis 的定义: 一个节点到他的子树中的叶子节点的最近距离。

    维护\(dis\)有什么用呢?

    当我们合并两个堆时,可能会导致堆退化成一条链,这样我们的询问操作的复杂度就会变成 O(n)。

    这显然不是我们想看到的。

    而左偏树就是靠 \(dis\) 来维护左右两颗子树的平衡。

    性质

    1. 节点的权值小于他儿子节点的取值。

    2. 左偏树的任意节点的左儿子的距离都要比右儿子大(不然他怎么叫左偏树呢雾

    3. 左偏树任意节点的距离等于右儿子的距离加一。

    证明如下:

    根据左偏性,可以得到他左儿子的距离要小于右儿子的距离,
    而左偏树中距离的定义是一个节点到离其最近的外节点的距离,故为Dis(RightSon)+1。

    1. 一个n个节点的左偏树的距离最大为 log(n+1)-1(不需要掌握

    证明如下:

    若左偏树的距离为一定值,则结点数最少的左偏树是完全二叉树。

    结点最少的话,就是左右儿子距离一样,这就是完全二叉树了。

    若一棵左偏树的距离为k,则这棵左偏树至少有 \(2^{k+1}-1\) 个节点。

    距离为k的完全二叉树高度也是k,节点数就是 \(2^{k+1}-1\) 个。

    这样就可以证明性质四了。

    因为 n>= \(2^{k+1}-1\),所以 k<=log(n+1)-1。

    操作

    一 合并操作

    首先,当我们要合并堆时,让要保持堆的性质,假设我们要维护小根堆,我们就要让根节点权值较小的放上边。

    然后再让另一个树与他的右子树合并。

    那么为什么要合并右子树呢???

    因为左子树的距离是大于右子树的,合并左子树显然不优。

    当我们合并完后,可能会出现这样一种情况,左子树的距离小于右子树,这时我们直接交换一下就行了。

    代码

    int merage(int x,int y)//返回根节点
    {
    	if(x == 0 || y == 0) return x + y;//如果有一个为空,直接返回,不用合并
    	if(tr[x].val > tr[y].val) swap(x,y);//要满足堆的性质
    	tr[x].rc = merage(tr[x].rc,y);//合并右子树和另外一棵树
    	fa[tr[x].rc] = x;
    	if(tr[tr[x].lc].dis < tr[tr[x].rc].dis) swap(tr[x].lc,tr[x].rc);//满足性质二
    	tr[x].dis = tr[tr[x].rc].dis + 1;//满足性质三
    	return x;
    }
    

    二 插入一个点

    一个点就相当于一棵树,直接合并就行了。

    代码同上。

    三 删除根节点

    我们要删根节点,等于把根节点孤立出来,这时候我们只需要合并左右两颗子树就行了

    代码

    void del(int x)
    {
    	tr[x].val = -1;//标记x已经被删除
    	fa[tr[x].lc] = tr[x].lc; fa[tr[x].rc] = tr[x].rc;//先把左右儿子的父亲都设为他自己,方便以后合并
    	fa[x] =  merage(tr[x].lc,tr[x].rc);//保持原有的父子关系不变
    }
    

    例题

    P3377 模板左偏树

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int N = 1e6+7;
    int n,m,opt,x,y;
    int fa[N],dis[N];
    struct node{
    	int val;
    	int lc,rc;
    	int fa,dis;
    }tr[N];
    int inline 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;
    }
    int find(int x){
        if(tr[x].fa == x) return x;
        else return tr[x].fa = find(tr[x].fa);
    }
    int merage(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);
    	}
    	tr[x].rc = merage(tr[x].rc,y);
    	tr[tr[x].rc].fa = x;
    	if(tr[tr[x].lc].dis < tr[tr[x].rc].dis) swap(tr[x].lc,tr[x].rc);
    	tr[x].dis = tr[tr[x].rc].dis + 1;
    	return x;
    }
    void del(int x){
    	tr[x].val = -1;
    	tr[tr[x].lc].fa = tr[x].lc, tr[tr[x].rc].fa = tr[x].rc;
    	tr[x].fa = merage(tr[x].lc,tr[x].rc);
    }
    int main(){
    	n = read(), m = read();
    	for(int i = 1; i <= n; i++){
    		tr[i].fa = i;
            tr[i].val = read();
    	}
    	while(m--){
    		opt = read();
    		if(opt == 1){
    			x = read(); y = read();
    			int fx = find(x) , fy = find(y);
    			if(tr[x].val == -1 || tr[y].val == -1) continue;
    			if(fx == fy) continue;
    			tr[fx].fa = tr[fy].fa = merage(fx,fy);
    		}
    		else{
    			x = read();
    			if(tr[x].val == -1) cout<<-1<<endl;
    			else{
    				int y = find(x);
    				printf("%d\n",tr[y].val);
    				del(y);
    			}
    		}
    	}
    	return 0;
    }
    

    P2713 罗马游戏

    左偏树的裸题,和模板差不多

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int N = 1e6+10;
    char opt;
    int n,t,x,y,fa[N];
    struct node{
    	int lc,rc;
    	int val,dis;
    }tr[N];
    inline int read()
    {
    	int s = 0, w = 1; char ch = getchar();
    	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9'){s = s * 10+ch -'0'; ch = getchar();}
    	return s * w;
    }
    int find(int x)
    {
    	if(fa[x] == x) return x;
    	else return fa[x] = find(fa[x]);
    }
    int merage(int x,int y)
    {
    	if(x == 0 || y == 0) return x + y;
    	if(tr[x].val > tr[y].val) swap(x,y);
    	tr[x].rc = merage(tr[x].rc,y);
    	fa[tr[x].rc] = x;
    	if(tr[tr[x].lc].dis < tr[tr[x].rc].dis) swap(tr[x].lc,tr[x].rc);
    	tr[x].dis = tr[tr[x].rc].dis + 1;
    	return x;
    }
    void del(int x)
    {
    	tr[x].val = -1;
    	fa[tr[x].lc] = tr[x].lc; fa[tr[x].rc] = tr[x].rc;
    	fa[x] =  merage(tr[x].lc,tr[x].rc);
    }
    int main()
    {
    	n = read();
    	for(int i = 1; i <= n; i++)
    	{
    		fa[i] = i;
    		tr[i].val = read();
    	}
    	t = read();
    	while(t--)
    	{
    		cin>>opt;
    		if(opt == 'M')
    		{
    			x = read(); y = read();
    			int xx = find(x), yy = find(y);
    			if(tr[x].val == -1 || tr[y].val == -1) continue;
    			if(xx == yy) continue;
    			fa[xx] = fa[yy] = merage(xx,yy);
    		}
    		else if(opt == 'K')
    		{
    			x = read();
    			int xx = find(x);
    			if(tr[x].val == -1)
    			{
    				printf("%d\n",0);
    				continue;
    			}
    			printf("%d\n",tr[xx].val);
    			del(xx);
    		}
    	}
    	return 0;
    }
    

    P1456 Monkey King

    每次打架可以看成是删除两个堆的堆顶,插入一个数,合并两个堆。

    左偏树随便维护一下就行。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int N = 1e5+10;
    int n,m,x,y,root,fa[N];
    struct node
    {
    	int lc,rc,dis,val;
    }tr[N];
    inline int read()
    {
        int s = 0, w = 1; char ch = getchar();
        while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
        while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
        return s * w;
    }
    int find(int x)
    {
    	if(fa[x] == x) return x;
    	else return fa[x] = find(fa[x]);
    }
    int merage(int x,int y)
    {
    	if(!x || !y) return x + y; 
    	if(tr[x].val < tr[y].val) swap(x,y);
    	tr[x].rc = merage(tr[x].rc,y);
    	fa[tr[x].rc] = x;
    	if(tr[tr[x].rc].dis > tr[tr[x].lc].dis) swap(tr[x].lc,tr[x].rc);
    	tr[x].dis = tr[tr[x].rc].dis + 1;
    	return x;
    }  
    int del(int x)
    {
    	fa[tr[x].lc] = tr[x].lc, fa[tr[x].rc] = tr[x].rc;
    	int root = merage(tr[x].lc,tr[x].rc);
    	fa[x] = root;
    	return root;
    } 
    int main()
    {
    	while(scanf("%d",&n) != EOF)
    	{
    		for(int i = 1; i <= n; i++) tr[i].lc = tr[i].rc = tr[i].dis = 0;
    		for(int i = 1; i <= n; i++) fa[i] = i, tr[i].val = read();
    		m = read();
    		for(int i = 1; i <= m; i++)
    		{
    			x = read(); y = read();
    			int fx = find(x), fy = find(y);
    			if(fx == fy) printf("%d\n",-1);
    			else
    			{
    				root = del(fx);
    				tr[fx].lc = tr[fx].rc = 0; fa[fx] = fx; tr[fx].val /= 2;
    				fx = merage(fx,root);
    				root = del(fy);
    				tr[fy].lc = tr[fy].rc = 0; fa[fy] = fy; tr[fy].val /= 2;
    				fy = merage(fy,root);
    				fx = merage(fx,fy);
    				printf("%d\n",tr[fx].val);
    			} 
    		}
    	}
    }
    

    ENDING

  • 相关阅读:
    poj 3068 Bridge Across Islands
    XidianOJ 1086 Flappy v8
    XidianOJ 1036 分配宝藏
    XidianOJ 1090 爬树的V8
    XidianOJ 1088 AK后的V8
    XidianOJ 1062 Black King Bar
    XidianOJ 1091 看Dota视频的V8
    XidianOJ 1098 突击数论前的xry111
    XidianOJ 1019 自然数的秘密
    XidianOJ 1109 Too Naive
  • 原文地址:https://www.cnblogs.com/genshy/p/13394970.html
Copyright © 2011-2022 走看看