【模板】左偏树(可并堆)
https://www.luogu.org/problemnew/show/3377
主要是删除堆顶元素后并查集关系的维护:
第一种方式(代码):
原来的堆顶是x,删除x后,合并x的左右子树l、r,新的堆顶为y
则令x的祖先指向y
堆顶的直接子节点在并查集中的祖先指向堆顶
这样在寻找l、r的祖先时,先找到x,再找到y
x实际已经删除,但保留在并查集中,充当l、r找祖先节点的桥梁
第二种方式:
并查集不路径压缩
删除x后,l的祖先指向l,r的祖先指向r
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; #define N 100001 using namespace std; struct node { int lc,rc; int key,dis; }e[N]; int fa[N]; bool cut[N]; void read(int &x) { x=0; int f=1; char c=getchar(); while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); } while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } x*=f; } int find(int i) { return fa[i]==i ? i : fa[i]=find(fa[i]); } int merge(int a,int b) { if(!a) return b; if(!b) return a; if(e[a].key>e[b].key) swap(a,b); e[a].rc=merge(e[a].rc,b); if(e[e[a].rc].dis>e[e[a].lc].dis) swap(e[a].lc,e[a].rc); if(!e[a].rc) e[a].dis=0; else e[a].dis=e[e[a].rc].dis+1; return a; } void erase(int x) { cut[x]=true; fa[x]=merge(e[x].lc,e[x].rc); fa[fa[x]]=fa[x]; } int main() { int n,m; read(n); read(m); for(int i=1;i<=n;++i) { read(e[i].key); fa[i]=i; } int ty,x,y; while(m--) { read(ty); if(ty==1) { read(x); read(y); if(cut[x]||cut[y]) continue; x=find(x); y=find(y); if(x!=y) fa[x]=fa[y]=merge(x,y); } else { read(x); if(cut[x]) { puts("-1"); continue; } x=find(x); cout<<e[x].key<<'\n'; erase(x); } } }
题目描述
如题,一开始有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所得的结果。