左偏树(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所得的结果。
本题为左偏树模板题; 我左偏树的第一题。
左偏树有合并,删除的操作。具体左偏树能做什么题,目前只知道有关合并的题,是可以用左偏树来做的,其他的以后再来补充。
在用左偏树的时候。
具体有三个框架
1.getf 即寻找祖先;
2.Merge 合并操作,如果是最小堆,则要满足x<y,最大堆反过来(这是我刚做这些题的时候的见解,目前认为就是这样)
然后进行合并的递归操作,最后则要满足左偏,即dis【x】>dis【y】;
为什么要左偏????? 这可能是左偏树最重要的思想了;
左偏之后,能保证右边的深度较小,别人创造的这一算法里,是往右子树进行合并操作,操作的时间复杂度自然是按右子树的深度来算;
所以为了保证时间复杂度较小(logn)便要在右子树深度大于左子树时,交换两者的值;
3.pop操作,这个操作,是剔除堆中的最大值或者最小值,然后再将他的左右子树合并,其中一个成为新的根。然后再将被剔除点的父亲定为新根
为什么要定为新根呢,因为可能在下面的点中有直接指向这个点的节点。所以要将这些点指向新的。
1 #include<cstdio> 2 #include<algorithm> 3 #include<string.h> 4 #include<math.h> 5 using namespace std; 6 const int maxn=1e5+10; 7 int val[maxn]; 8 int f[maxn]; 9 int ch[maxn][2]; 10 int dis[maxn]; 11 int getf(int x) //标准并查集 12 { 13 if(f[x]==x) return x; 14 else{ 15 f[x]=getf(f[x]); 16 return f[x]; 17 } 18 } 19 int Merge(int x,int y) 20 { 21 if(!x||!y) return x+y; //到底了; 22 //保证最小堆性质 后面这个不懂 23 if(val[x]>val[y]||(val[x]==val[y]&&x>y)) swap(x,y); 24 //这个大概就是创这个算法的人的习惯了,将其定在右子树。 25 //然后再在下面进行操作来满足偏左树的性质; 26 ch[x][1]=Merge(ch[x][1],y); 27 f[ch[x][1]]=x; //并查集操作; 28 //满足偏左; 29 if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][0],ch[x][1]); 30 //这个是偏左树的性质,想想就知道是对的。 31 dis[x]=dis[ch[x][1]]+1; 32 return x; 33 } 34 int main() 35 { 36 int n,m; 37 scanf("%d%d",&n,&m); 38 for(int i=1;i<=n;i++){ 39 scanf("%d",&val[i]); 40 f[i]=i; 41 } 42 while(m--){ 43 int ope; 44 scanf("%d",&ope); 45 if(ope==1){ 46 int t1,t2; 47 scanf("%d%d",&t1,&t2); 48 if(val[t1]==-1||val[t2]==-1) continue; 49 t1=getf(t1); 50 t2=getf(t2); 51 if(t1==t2) continue; 52 Merge(t1,t2); 53 } 54 else{ 55 int t; 56 scanf("%d",&t); 57 if(val[t]==-1){ 58 printf("-1\n"); 59 continue; 60 } 61 t=getf(t); 62 printf("%d\n",val[t]); 63 //这里是pop的操作,将被T出的点定为-1; 64 val[t]=-1; 65 //再将原本的根指向新根,让其他原本指向旧根的点能继续指向新根 66 f[ch[t][0]]=f[ch[t][1]]=f[t]=Merge(ch[t][0],ch[t][1]); 67 //将旧根的左右儿子以及dis清零 68 ch[t][0]=ch[t][1]=dis[t]=0; 69 } 70 } 71 return 0; 72 }