左偏树(可并堆) [模板]
好一顿调试……
左偏树,又叫可并堆,顾名思义,它是支持合并的堆。
性质不多讲。
下文堆默认为小根堆
需维护v[i] //i节点的值
ls[i] //i的左儿子
rs[i] //i的右儿子
fa[i] //i所在堆的根
dis[i] //以i为根节点的树的距离(这里不多讲距离有什么用)
vis[i] //i是否被删除了 (这个不是必须维护的,可以有其他替代手段,这里给出一个例子:将v[i]=-1,代替vis数组的作用)
可支持的操作:
1.合并两个堆
我们设两个堆的根节点为 x,y
则合并操作为 merger(x,y)
若 x<y 则合成后的新堆顶为 x ,为了方便,若 x>y 则交换 x,y,变为 x<y
这样根节点确定后,我们可以继续合并 x的右子树和y ,将合并出来的东西替换掉原来 x 的右子树
这样进行递推合并即可
每次合并需要维护的内容 :
dis[x],fa[ls[x]],fa[rs[x]]
2.删除堆顶
可以将左右两个儿子合并成新的堆
需要维护的东西:
fa[ls[x]]=ls[x]
fa[rs[x]]=rs[x]
vis[x]=1
fa[x]=merge(ls[x],rs[x])//注意:根节点的祖先连到新的堆的根,这样才能保证并查集不断层
代码:
#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #include <cmath> #define MAXN 100011 using namespace std; int n,m; int ls[MAXN],rs[MAXN],dis[MAXN],v[MAXN],fa[MAXN],vis[MAXN]; inline int read(){ int x=0; char ch=getchar(); while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x; } int merger(int x,int y){ if(!x||!y) return x|y; if(v[x]>v[y]||(v[x]==v[y]&&x>y)) swap(x,y); rs[x]=merger(rs[x],y); fa[rs[x]]=x; if(dis[ls[x]]<dis[rs[x]]) swap(ls[x],rs[x]); dis[x]=dis[rs[x]]+1; return x; } int find(int x){ if(fa[x]==x) return x; return fa[x]=find(fa[x]); } void _del(int x){ fa[ls[x]]=ls[x]; fa[rs[x]]=rs[x]; fa[x]=merger(ls[x],rs[x]); vis[x]=1; return ; } int main(){ n=read(),m=read(); for(int i=1;i<=n;i++){ v[i]=read(); fa[i]=i; } for(int i=1;i<=m;i++){ int k=read(),x=read(); if(k==1){ int y=read(); if(vis[x]||vis[y]) continue ; merger(find(x),find(y)); } else{ if(vis[x]) cout<<"-1\n"; else{ cout<<v[find(x)]<<endl; _del(find(x)); } } } return 0; }