关于树论【左偏树】

还记得当年坐在OZY大佬旁边被D的日子。。才发现现在妙已经变成权限题做不了(怕是要被DS)只能补补左偏树聊以自慰了。

这个东西呢其实也是堆的一种(也叫左偏堆),可以理解为维护大(小)根堆的,堆顶就是最大(小)值用d表示,然后l,r是左右孩子节点,c是管理人数。至于为什么叫做左偏树呢,是因为他一个奇怪的定义:左边的子树的节点数一定大于右边的子树的节点数,这样有什么好处呢?显然,我们如果插入节点从左往右,那就可以保证树的平衡。

其实这样看来,这个东西挺普通的,只是方便求最大(小)值,或者再拓展一下,管理多个集合,但是,它有一个关键的操作,就是Merge——合并!他可以支持两个左偏树的合并,具体怎么做呢?跟插入一个点是一样的,在右边插入,然后通过交换左右孩子,继续保持左偏性质,所以这个有一个限制——左右孩子可以交换,就是说不像伸展树类似的,左大右小什么的。

bzoj1455(现在也变成权限题了)时光荏再,物是人非啊。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
struct heap
{
    int l,r,c,d;
    heap()
    {
        l=0;r=0;c=0;
    }
}h[1100000];
int Merge(int x,int y)
{
    if(x==0||y==0)return x+y;
    if(h[x].d>h[y].d)swap(x,y);//维护小根堆 
    h[x].r=Merge(h[x].r,y);//让他到右边合并,令树平衡 
    if(h[h[x].l].c<h[h[x].r].c)swap(h[x].l,h[x].r);//保持左偏 
    h[x].c=h[h[x].r].c+1;//维护 
    return x; 
}
int fa[1100000];
int findfa(int x)//用并查集来维护每一个团 
{
    if(fa[x]==x)return x;
    fa[x]=findfa(fa[x]);return fa[x];
}
bool v[1100000];//人有没有被杀 
char ss[5];
int main()
{
    int n,m,x,y;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        fa[i]=i;
        scanf("%d",&h[i].d);
    }
    memset(v,false,sizeof(v)); 
    scanf("%d",&m);
    while(m--)
    {
        scanf("%s",ss+1);
        if(ss[1]=='M')
        {
            scanf("%d%d",&x,&y);
            if(v[x]==true||v[y]==true)continue; 
            int fx=findfa(x),fy=findfa(y);
            if(fx==fy)continue;//是否在一个团里面 
            fa[fx]=fa[fy]=Merge(fx,fy);//合并两个堆,维护并查集,祖先是值最小的人 
        }
        else
        {
            scanf("%d",&x);
            if(v[x]==true){printf("0
");continue;}
            int k=findfa(x);v[k]=true;//杀人 
            printf("%d
",h[k].d);
            fa[k]=Merge(h[k].l,h[k].r);//祖先是新的值最小的人 
            fa[fa[k]]=fa[k];
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/AKCqhzdy/p/7619895.html