引言
对于维护一串数的最大值,最小值,插入,查询,删除优先值。
我们仅需要使用二叉堆也就是STL中的优先队列即可维护!
但如果有多个堆并且需要我们对堆进行合并,该怎么办?
于是引入可合并堆。
可合并堆有四种,这里介绍左偏树。
支持合并,查询,插入,删除优先值操作。
性质
1:根节点大于(小于)任何一个子节点(堆的性质)
2:节点的左儿子的距离不小于右儿子的距离。
3:根节点的距离等于右儿子距离+1
4:一个n个节点的左偏树最大距离为
log(n+1)+1
实现操作
1.合并
我们假设A的根节点小于等于B的根节点(否则交换A,B),把A的根节点作为新树C的根节点,剩下的事就是合并A的右子树和B了。
合并了A的右子树和B之后,A的右子树的距离可能会变大,当A的右子树 的距离大于A的左子树的距离时,性质二会被破坏。在这种情况下,我们只须要交换A的右子树和左子树。
而且因为A的右子树的距离可能会变,所以要更新A的距离=右儿子距离+1。这样就合并完了。
2.插入
相当于一个节点的堆和n个节点的堆合并
3.删除优先值
相当于删除优先值后左右子树合并。
代码实现
#include <cstdio>
#define il inline
using namespace std;
const int maxm=110000;
il int read()
{
int x=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*w;
}
int son[maxm][2],val[maxm],dis[maxm],fa[maxm],n,m;
il void swap(int &x,int &y)
{
int tmp=x;
x=y,y=tmp;
}
int merge(int x,int y)
{
if (x==0||y==0)
return x+y;
if (val[x]>val[y]||(val[x]==val[y]&&x>y))
swap(x,y);
son[x][1]=merge(son[x][1],y);
fa[son[x][1]]=x;
if (dis[son[x][0]]<dis[son[x][1]])
swap(son[x][0],son[x][1]);
dis[x]=dis[son[x][1]]+1;
return x;
}
il int getf(int x)
{
while(fa[x]!=x) x=fa[x];
return x;
}
il void pop(int x)
{
val[x]=-1;
fa[son[x][0]]=son[x][0],fa[son[x][1]]=son[x][1];
merge(son[x][0],son[x][1]);
}
int main()
{
n=read(),m=read();
dis[0]=-1;
for (int i=1;i<=n;i++)
val[i]=read(),fa[i]=i;
for(int i=1,x,y,opt;i<=m;i++)
{
opt=read();
if(opt==1)
{
x=read(),y=read();
if(val[x]==-1||val[y]==-1||x==y)
continue;
int fx=getf(x),fy=getf(y);
if(fx!=fy)
merge(fx,fy);
}
else
{
x=read();
if(val[x]==-1)
printf("-1\n");
else
{
int fa=getf(x);
printf("%d\n",val[fa]);
pop(fa);
}
}
}
return 0;
}