zoukankan      html  css  js  c++  java
  • 左偏树(可并堆)实现

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

    题目描述
    如题,一开始有N个小根堆,每个堆包含且仅包含一个数。接下来需要支持两种操作:
    
    操作1: 1 x y 将第x个数和第y个数所在的小根堆合并(若第x或第y个数已经被删除或第x和第y个数在用一个堆内,则无视此操作)
    
    操作2: 2 x 输出第x个数所在的堆最小数,并将其删除(若第x个数已经被删除,则输出-1并无视删除操作)
    
    输入输出格式
    输入格式:
    第一行包含两个正整数N、M,分别表示一开始小根堆的个数和接下来操作的个数。
    
    第二行包含N个正整数,其中第i个正整数表示第i个小根堆初始时包含且仅包含的数。
    
    接下来M行每行2个或3个正整数,表示一条操作,格式如下:
    
    操作1 : 1 x y
    
    操作2 : 2 x
    
    输出格式:
    输出包含若干行整数,分别依次对应每一个操作2所得的结果。
    
    输入输出样例
    输入样例#15 5
    1 5 4 2 3
    1 1 5
    1 2 5
    2 2
    1 4 2
    2 2
    输出样例#11
    2
    说明
    当堆里有多个最小值时,优先删除原序列的靠前的,否则会影响后续操作1导致WA。
    
    时空限制:1000ms,128M
    
    数据规模:
    
    对于30%的数据:N<=10,M<=10
    
    对于70%的数据:N<=1000,M<=1000
    
    对于100%的数据:N<=100000,M<=100000
    
    样例说明:
    
    初始状态下,五个小根堆分别为:{1}、{5}、{4}、{2}、{3}。
    
    第一次操作,将第1个数所在的小根堆与第5个数所在的小根堆合并,故变为四个小根堆:{1,3}、{5}、{4}、{2}。
    
    第二次操作,将第2个数所在的小根堆与第5个数所在的小根堆合并,故变为三个小根堆:{1,3,5}、{4}、{2}。
    
    第三次操作,将第2个数所在的小根堆的最小值输出并删除,故输出1,第一个数被删除,三个小根堆为:{3,5}、{4}、{2}。
    
    第四次操作,将第4个数所在的小根堆与第2个数所在的小根堆合并,故变为两个小根堆:{2,3,5}、{4}。
    
    第五次操作,将第2个数所在的小根堆的最小值输出并删除,故输出2,第四个数被删除,两个小根堆为:{3,5}、{4}。
    
    故输出依次为1、2
    View Code

    左偏树模板

    什么是左偏树呢?首先,从名字上看,它是一棵树。其实它还是一棵二叉树。它的节点上存4个值:左、右子树的地址,权值,距离。

    权值就是堆里面的值。距离表示这个节点到它子树里面最近的叶子节点的距离。叶子节点距离为0。

    既然是一种特殊的数据结构,那肯定有它自己的性质。左偏树有几个性质(小根为例)。

    性质一:节点的权值小于等于它左右儿子的权值。

    堆的性质,很好理解。

    性质二:节点的左儿子的距离不小于右儿子的距离。

    在写平衡树的时候,我们是确保它的深度尽量的小,这样访问每个节点都很快。但是左偏树不需要这样,它的目的是快速提取最小节点和快速合并。

    所以它并不平衡,而且向左偏。但是距离和深度不一样,左偏树并不意味着左子树的节点数或是深度一定大于右子树。

    性质三:节点的距离等于右儿子的距离+1。

    没什么好说的= =

    性质四:一个n个节点的左偏树距离最大为 log(n+1)-1log(n+1)1

    这个怎么证明呢?我们可以一点一点来。

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

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

    若一棵左偏树的距离为k,则这棵左偏树至少有2k+11 个节点。

    距离为k的完全二叉树高度也是k,节点数就是 2k+11 。

    这样就可以证明性质四了。因为 n>=2k+11 ,所以 k<=log(n+1)1

    有了性质,我们来讲讲它的操作。

    0.查找最小值

    我们维护了小根堆,不停地从当前节点找父亲就能找到最小值(根),但如果树退化,查询就会变成O(N)的

    于是我们用f[x]表示第i个点所在的树的根,用并查集findset的方法查找根

    inline int get(int x){
        return f[x]==x?x:f[x]=get(f[x]);
    }

    1.合并

    我们假设A的根节点小于等于B的根节点(否则交换A,B),把A的根节点作为新树C的根节点,剩下的事就是合并A的右子树和B了。

    合并了A的右子树和B之后,A的右子树的距离可能会变大,当A的右子树 的距离大于A的左子树的距离时,性质二会被破坏。在这种情况下,我们只须要交换A的右子树和左子树。

    而且因为A的右子树的距离可能会变,所以要更新A的距离=右儿子距离+1。这样就合并完了。

    我们来分析一下复杂度。我们可以看出每次我们都把它的右子树放下去合并。

    因为一棵树的距离取决于它右子树的距离(性质三),所以拆开的过程不会超过它的距离。

    根据性质四,不会超过log(nx+1)+log(ny+1)2 ,复杂度就是O(lognx+logny)

    int merg(int x,int y){
        if(!x||!y){
            return x+y;
        }
        if(val[x]>val[y]||(val[x]==val[y]&&x>y)){
            swap(x,y);
        }
        ch[x][1]=merg(ch[x][1],y);
        f[ch[x][0]]=f[ch[x][1]]=f[x]=x;
        if(dis[ch[x][0]]<dis[ch[x][1]]){
            swap(ch[x][0],ch[x][1]);
        }
        dis[x]=dis[ch[x][1]]+1;
        return x;
    }

    2.插入

    插入一个节点,就是把一个点和一棵树合并起来。

    因为其中一棵树只有一个节点,所以插入的效率是 O(logn)

    3.删除最小/大点

    因为根是最小/大点,所以可以直接把根的两个儿子合并起来。

    因为只合并了一次,所以效率也是 O(logn) 。

    inline void del(int x){
        val[x]=-1;
        f[ch[x][0]]=ch[x][0];
        f[ch[x][1]]=ch[x][1];
        f[x]=merg(ch[x][0],ch[x][1]);
    }

    代码

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 typedef long long LL;
     4 const int INF=1e9+7,MAXN=1e5+1,MAXM=1e5+1;
     5 int N,M;
     6 int val[MAXN],dis[MAXN],f[MAXN],ch[MAXN][2];
     7 int merg(int x,int y){
     8     if(!x||!y){
     9         return x+y;
    10     }
    11     if(val[x]>val[y]||(val[x]==val[y]&&x>y)){
    12         swap(x,y);
    13     }
    14     ch[x][1]=merg(ch[x][1],y);
    15     f[ch[x][0]]=f[ch[x][1]]=f[x]=x;
    16     if(dis[ch[x][0]]<dis[ch[x][1]]){
    17         swap(ch[x][0],ch[x][1]);
    18     }
    19     dis[x]=dis[ch[x][1]]+1;
    20     return x;
    21 }
    22 inline int get(int x){
    23     return f[x]==x?x:f[x]=get(f[x]);
    24 }
    25 inline void del(int x){
    26     val[x]=-1;
    27     f[ch[x][0]]=ch[x][0];
    28     f[ch[x][1]]=ch[x][1];
    29     f[x]=merg(ch[x][0],ch[x][1]);
    30 }
    31 int main(){
    32     scanf("%d%d",&N,&M);
    33     dis[0]=-1;
    34     for(int i=1;i<=N;i++){
    35         f[i]=i;
    36         scanf("%d",val+i);
    37     }
    38     for(int i=1;i<=M;i++){
    39         int sign,ii,jj;
    40         scanf("%d",&sign);
    41         if(sign==1){
    42             scanf("%d%d",&ii,&jj);
    43             if(val[ii]==-1||val[jj]==-1||ii==jj){
    44                 continue;
    45             }
    46             int f1=get(ii),f2=get(jj);
    47             merg(f1,f2);
    48         }else{
    49             scanf("%d",&ii);
    50             if(val[ii]==-1){
    51                 puts("-1");
    52             }else{
    53                 ii=get(ii);
    54                 printf("%d
    ",val[ii]);
    55                 del(ii);
    56             }
    57         }
    58     }
    59     return 0;
    60 }
  • 相关阅读:
    直方图内最大矩阵
    P1578 奶牛浴场
    P1569 [USACO11FEB]属牛的抗议Generic Cow Prote…
    P1566 加等式
    P1564 膜拜
    P1541 乌龟棋
    P1537 弹珠
    Response.AddHeader使用实例
    LSPCI具体解释分析
    介绍一款开源的类Excel电子表格软件
  • 原文地址:https://www.cnblogs.com/guoshaoyang/p/10631681.html
Copyright © 2011-2022 走看看