题意:合并堆及删除堆中最小元素。
思路:采用一般的二叉堆可以实现删除操作,但是要合并的话就会爆,因此引进了左偏树这个数据结构,可在logn时间内实现合并操作。
左偏树的性质:
1.堆的性质
2.左子节点的距离大于右子节点的距离
正是利用这些性质实现堆的快速合并。
代码如下:
(其中并查集的路径压缩会破坏掉树的结构,因此在路径压缩后要将所有相邻节点的父节点都置为当前的根节点)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 100050
int n,m;
int val[maxn],f[maxn],dis[maxn],ls[maxn],rs[maxn];
bool vis[maxn];
inline void ini()
{
for(int i=1;i<=n;i++)
{
f[i]=i;
}//并查集的时候使用;
memset(vis,false,sizeof(vis));
memset(ls,0,sizeof(ls));//0表示为空节点;
memset(rs,0,sizeof(rs));
}
int merge(int x,int y)
{
if(!x||!y)
{
return x+y;
}//其中一棵树为空的时候的处理;
if((val[x]>val[y])||(val[x]==val[y]&&x>y))
{
swap(x,y);
}//按照小根堆的方式,将权值大的树并到权值小的树的右子树上;
rs[x]=merge(rs[x],y);//递归方式继续合并;
if(dis[ls[x]]<dis[rs[x]])//左偏树的性质,左子树距离要大于右子树;
{
swap(ls[x],rs[x]);//合并过程可能发生swap导致子节点发生了变化;
}
f[ls[x]]=f[rs[x]]=f[x]=x;
if(rs[x]==0)
{
dis[x]=0;
}
else
{
dis[x]=dis[rs[x]]+1;
}
return x;
}
int del(int x)//删除根节点;
{
vis[x]=true;
f[ls[x]]=ls[x];
f[rs[x]]=rs[x];
int now=merge(ls[x],rs[x]);
f[x]=f[ls[x]]=f[rs[x]]=now;
return now;
}
int find(int x)//并查集;
{
return f[x]==x?x:f[x]=find(f[x]);
}
int main()
{
int i,j;
int op,a,b;
scanf("%d%d",&n,&m);
ini();
for(i=1;i<=n;i++)
{
scanf("%d",&val[i]);
}
for(i=1;i<=m;i++)
{
scanf("%d",&op);
if(op==1)
{
scanf("%d%d",&a,&b);
if(vis[a]||vis[b])
{
continue;
}
int fa=find(a);
int fb=find(b);
if(fa!=fb)
{
merge(fa,fb);
}
}
else
{
scanf("%d",&a);
if(vis[a])
{
printf("-1\n");
continue;
}
int fa=find(a);
printf("%d\n",val[fa]);
del(fa);
}
}
return 0;
}