『左偏树』学习笔记
作者:互联网
背景
今天 LJ 莫名其妙的给我们讲了左偏树(其实只是给我们放了一个往期讲课视频).
讲的比较好的 blog .(其实是看了 blog 才懂的)
首先,我们必须明确,左偏树是一种 可以合并( merge )的 堆 式数据结构. 其合并时间复杂度为 \(O(\log_2n).\)
定义
左偏树较之堆多了一个 \(dis\) 距离 .我们定义 \(dis_x\) 为子树中,节点 \(x\) 到最近的 外节点 (左儿子或右儿子是空结点的结点) 的距离. 特别地,空节点的距离为 \(-1\) .
为行文方便,现将文章中出现的英文及其对应意义列举如下(与左偏树无关)
\(val_x\) : 表示节点 \(x\) 对应的数值.
\(dis_x\) : 见距离定义.
\(ls_x\) : 节点 \(x\) 的左孩子.
\(rs_x\) : 节点 \(x\) 的右孩子.
\(rt_x\) : 节点 \(x\) 所在树的根节点
性质
堆性质 : 对于每个节点 \(x\) ,满足 \(val_x \le val_{ls_x},val_{rs_x}\) .
左偏性质 : 对于每个节点 \(x\) ,满足 \(dis_{ls_x}\le dis_{rs_x}\) .
基本结论
1.\(dis_x=dis_{rs_x}+1\) .
2
. 距离为 \(n\) 的左偏树至少有 \(2^{n+1}-1\) 个节点,此时该树为满二叉树.
3
.有 \(n\) 个节点的左偏树距离是 \(\log_2n\) .
基本操作:合并 merge
我们定义 merge(x,y)
为合并两棵以 \(x,y\) 为根节点的左偏树 . 返回合并后的根节点.
1.既然它要满足堆性质,那么 \(x,y\) 中小的肯定是合并后的根节点,方便起见这里我们默认 \(val_x\le val_y\),. 若不,则 swap(x,y)
.
2.它还要满足左偏性质,那么我们不妨将 \(y\) 与 \(rs_x\) 合并(即 merge(rs[x],y)
),如果 \(dis_{ls_x}< dis_{rs_x}\),就 swap(ls[x],rs[y])
.
3.重复上述操作,直到 \(x,y\) 中有一个节点为空节点, 返回 \(x+y\) .
由于左偏树高 \(\log_2n\) ,所以合并的复杂度为 \(O(\log_2n)\) .
code:
int merge(int x,int y)
{
if(!x||!y) return x+y;
if(t[y]<t[x]) swap(x,y);
rs[x]=merge(rs[x],y);
if(dis[ls[x]]<dis[rs[x]]) swap(ls[x],rs[x]);
dis[x]=dis[rs[x]]+1;
return x;
}
其他操作
插入
插入一个元素 \(x\) . 将其看做一棵树与原左偏树合并即可. 时间复杂度 \(O(\log_2 n)\) .
求最小值
因左偏堆满足堆性质,所以直接返回堆顶即可 . 时间复杂度 \(O(1)\) .
删除最小值
merge(ls[x],rs[x])
. 时间复杂度 \(O(\log_2 n)\) .
code:
inline void pop(int x)
{
rt[ls[x]]=rt[rs[x]]=rt[x]=merge(ls[x],rs[x]);
ls[x]=rs[x]=dis[x]=0;
}
给定一节点,求所在树的根节点
我们可以暴力,一层一层往上,但是有可能会出现刚好是一条链的情况。此时复杂度为 \(O(n)\) . zbc!
于是我们可以用 并查集 , 路径压缩 !
code:
int find(int x)
{
return rt[x]==x?x:rt[x]=find(rt[x]);
}
如此一来,我们就需要维护 \(rt_x\) .
每次 merge 时, rt[x]=rt[y]=merge(x,y)
.
每次 pop 时,rt[ls[x]]=rt[rs[x]]=rt[x]=merge(ls[x],rs[x])
.因为 \(x\) 有可能是某个 \(rt_i\) 的值,所以 \(rt_x\) 也要改变 .
删除任意已知节点
没有码过,就听 LJ 口胡了一下 .
先正常 pop , 然后一路更新 \(dis\) 就好了.
例题
模板.
一次 A .
code:
#include<bits/stdc++.h>
#define FOR(i,j,k) for(int i=(j);i<=(k);i++)
using namespace std;
int n,m;
int ls[100005],rs[100005],dis[100005],rt[100005];
bool del[100005];
struct point{
int id,val;
inline bool operator <(point x) const{
return val==x.val?id<x.id:val<x.val;
}
}t[100005];
int find(int x)
{
return rt[x]==x?x:rt[x]=find(rt[x]);
}
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
if(t[y]<t[x]) swap(x,y);
rs[x]=merge(rs[x],y);
if(dis[ls[x]]<dis[rs[x]]) swap(ls[x],rs[x]);
dis[x]=dis[rs[x]]+1;
return x;
}
inline void pop(int x)
{
rt[ls[x]]=rt[rs[x]]=rt[x]=merge(ls[x],rs[x]);
ls[x]=rs[x]=dis[x]=0;
}
int main()
{
dis[0]=-1;
n=read(),m=read();
for(int i=1;i<=n;i++)
t[i].val=read(),rt[i]=i,t[i].id=i;
while(m--)
{
int op=read(),x=read();
if(op==1)
{
int y=read();
if(del[x]||del[y]) continue;
x=find(x),y=find(y);
if(x!=y) rt[x]=rt[y]=merge(x,y);
}
if(op==2)
{
if(del[x])
{
puts("-1");
continue;
}
x=find(x);
printf("%d\n",t[x].val);
del[x]=true;
pop(x);
}
}
return 0;
}
code:
#include<bits/stdc++.h>
#define FOR(i,j,k) for(int i=(j);i<=(k);i++)
using namespace std;
int n,m;
int ls[1000005],rs[1000005],rt[1000005],dis[1000005];
bool del[1000005];
struct point{
int id,val;
inline bool operator <(point x) const{
return val==x.val?id<x.id:val<x.val;//注意,这里第一次写错了,写成了 id==x.id?val<x.val:if<x.id .
}
}t[1000005];
int find(int x)
{
return rt[x]==x?x:rt[x]=find(rt[x]);
}
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
if(t[y]<t[x]) swap(x,y);
rs[x]=merge(rs[x],y);
if(dis[ls[x]]<dis[rs[x]]) swap(ls[x],rs[x]);
dis[x]=dis[rs[x]]+1;
return x;
}
inline void pop(int x)
{
rt[ls[x]]=rt[rs[x]]=rt[x]=merge(ls[x],rs[x]);
ls[x]=rs[x]=dis[x]=0;
}
int main()
{
dis[0]=-1;
n=read();
for(int i=1;i<=n;i++) t[i].val=read(),rt[i]=i,t[i].id=i;
m=read();
while(m--)
{
char op;
cin>>op;
if(op=='M')//merge
{
int x=read(),y=read();
if(del[x]||del[y]) continue;
x=find(x),y=find(y);
if(x==y) continue;
rt[x]=rt[y]=merge(x,y);
}
if(op=='K')
{
int x=read();
if(del[x])
{
puts("0");
continue;
}
x=find(x);
printf("%d\n",t[x].val);
del[x]=true;
pop(x);
}
}
return 0;
}
很板.
无非就是小根堆变为大根堆.
无它.
#include<bits/stdc++.h>
#define FOR(i,j,k) for(int i=(j);i<=(k);i++)
using namespace std;
int n,m;
int ls[100005],rs[100005],rt[100005],dis[100005];
struct point{
int id,val;
inline bool operator <(point x) const{
return val==x.val?id<x.id:val<x.val;
}
}t[100005];
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(t[x]<t[y]) swap(x,y);//大根堆
rs[x]=merge(rs[x],y);
if(dis[ls[x]]<dis[rs[x]]) swap(ls[x],rs[x]);
dis[x]=dis[rs[x]]+1;
return x;
}
inline int pop(int x)
{
int res=merge(ls[x],rs[x]);
rt[ls[x]]=rt[rs[x]]=rt[x]=res;
ls[x]=rs[x]=dis[x]=0;
return res;
}
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
int main()
{
while(cin>>n)
{
dis[0]=-1;
for(int i=1;i<=n;i++)
t[i].val=read(),rt[i]=i,t[i].id=i,dis[i]=ls[i]=rs[i]=0;
m=read();
while(m--)
{
int x=read(),y=read();
int p=find(x),q=find(y);
if(p==q)
{
puts("-1");
continue;
}
x=pop(p),y=pop(q);
t[p].val>>=1,t[q].val>>=1;
rt[x]=rt[p]=merge(p,x),rt[y]=rt[q]=merge(q,y);
p=find(p),q=find(q);
rt[p]=rt[q]=merge(p,q);
printf("%d\n",t[rt[p]].val);
}
}
return 0;
}
\(\mathfrak{To}\) \(\mathfrak{Be}\) \(\mathfrak{Continued......}\)
标签:rt,rs,笔记,学习,merge,dis,节点,左偏 来源: https://www.cnblogs.com/LJC001151/p/14396158.html