左偏树(可并堆)实现

原题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\n",val[ii]);
55                 del(ii);
56             }
57         }
58     }
59     return 0;
60 }

 

posted @ 2019-03-31 15:54  guoshaoyang  阅读(137)  评论(0编辑  收藏  举报