hsfzLZH1
2019-07-10 17:25:04
左偏树是 一种 支持在
外结点 :左儿子或右儿子是空结点的结点。
距离 : 一个结点
左偏树具有 堆性质 ,即若其满足小根堆的性质,则对于每个结点
左偏树具有 左偏性质 ,即对于每个结点
结点
距离为
有
左偏树最基本的操作是合并操作。
定义 merge(x,y)
为合并两棵分别以
首先不考虑左偏性质,我们描述一下合并两个具有堆性质的树的过程。假设我们要合并的是小根堆。
若
将
重复以上操作,如果
令
每次随机选择
左偏树。
由于左偏树中左儿子的距离大于右儿子的距离,我们 每次将
但是,两棵左偏树按照上述方法合并后,可能不再保持左偏树的左偏性质。在每次合并完之后,判断对结点
由于合并后的树既满足堆性质又满足左偏性质,所以合并后的树仍然是左偏树。
int merge(int x,int y)
{
if(!x||!y)return x+y;
if(v[y]<v[x])swap(x,y);
rc[x]=merge(rc[x],y);
if(dist[lc[x]]<dist[rc[x]])swap(lc[x],rc[x]);
dist[x]=dist[rc[x]]+1;
return x;
}
新建一个值等于插入值的结点,将该节点与左偏树合并即可。时间复杂度
由于左偏树的堆性质,左偏树上的最小值为其根节点的值。
等价于删除左偏树的根节点。合并根节点的左右儿子即可。记得维护已删除结点的信息。
我们可以记录每个结点的父亲结点
int findrt(int x)
{
if(fa[x])return findrt(fa[x]);
return x;
}
注意,虽然左偏树的距离是
上面的代码让你想到了什么?并查集。我们同样可以用 路径压缩 的方式,求一个结点所在左偏树的根节点。
int find(int x){return rt[x]==x?x:rt[x]=find(rt[x]);}
使用这种写法,需要维护 rt[x]
的值。
在合并两个结点 rt[x]=rt[y]=merge(x,y)
。
在删除左偏树中的最小值时,令 rt[lc[x]]=rt[rc[x]]=rt[x]=merge(lc[x],rc[x])
,因为 rt
的值等于 rt[x]
也要指向删除后的根节点。
由于
路径压缩后,可以在
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100010;
int n,m,op,x,y;
int lc[maxn],rc[maxn],dist[maxn],rt[maxn];
bool tf[maxn];
struct node
{
int id,v;
bool operator<(node x)const{return v==x.v?id<x.id:v<x.v;}
}v[maxn];
int find(int x){return rt[x]==x?x:rt[x]=find(rt[x]);}
int merge(int x,int y)
{
if(!x||!y)return x+y;
if(v[y]<v[x])swap(x,y);
rc[x]=merge(rc[x],y);
if(dist[lc[x]]<dist[rc[x]])swap(lc[x],rc[x]);
dist[x]=dist[rc[x]]+1;
return x;
}
int main()
{
dist[0]=-1;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&v[i].v),rt[i]=i,v[i].id=i;
while(m--)
{
scanf("%d%d",&op,&x);
if(op==1)
{
scanf("%d",&y);
if(tf[x]||tf[y])continue;
x=find(x);y=find(y);
if(x!=y)rt[x]=rt[y]=merge(x,y);
}
if(op==2)
{
if(tf[x]){printf("-1\n");continue;}
x=find(x);
printf("%d\n",v[x].v);
tf[x]=true;
rt[lc[x]]=rt[rc[x]]=rt[x]=merge(lc[x],rc[x]);
lc[x]=rc[x]=dist[x]=0;
}
}
return 0;
}