[StudyNotes] 左偏树
来源:互联网 发布:中国的未来在哪里 知乎 编辑:程序博客网 时间:2024/05/18 20:05
什么是左偏树?
左偏树(
相比于
左偏树上的每个节点不仅保存了
这里的
首先定义一个“外节点”的概念
如果一个节点
x ,它的左子树或者右子树为空,即还能在x 这个节点合并上另一个堆,就称x 为外节点
对于一个节点
定义外节点的
- 举个栗子:
图中的数字代表每个节点的
左偏树的性质
这里我用 struct
来存一棵左偏树
struct Leftist_Tree { int val,lc,rc,dis; bool operator < (const Leftist_Tree &q)const { return val<q.val; }};Leftist_Tree t[MAXN];
这里以小根堆为例
性质 1
设
fa[x] 表示x 节点的父亲,那么显然有t[fa[x]].val<t[x].val (堆的性质)
性质 2
节点的左儿子的距离不小于右儿子的距离
即
左偏树,字面上的意思就是向左偏的树,也就是说这棵树左边的节点数一定比较多
那这样的话,每次从右子树找外节点一定比从左子树找外节点快
根据这个还可以得出一个推论:
一个节点的左子树和右子树都是左偏树
性质 3
对于一个有右儿子的节点
x ,有t[x].dis=t[t[x].rc].dis+1
也就是说, 一个节点的
为了让这个性质对没有右儿子的节点也满足,我们定义空节点的
这样性质3就可以表示为
实在不懂的话,看上面的图的
左偏树的一些骚操作
Merge
上图(图中的数字表示每个节点的
我们要现在合并这两个左偏树,第一步就是找到一个外节点,然后把它并上去
在 性质2 当中我们提到
每次从右子树找外节点一定比从左子树找外节点快
所以我们每次都贪心的找右子树
从根节点开始,
这时候重复上面的过程,第一个从右子树找到的外节点是
这个时候好像是合并完成了,但是其实没有,因为这个时候你会发现这不是左偏树了
我们合并出了一个假的左偏树,GG
我们来想办法让这棵树重新左偏,我们先从
这时我们为了让树左偏,直接
这是很显然也是很简单的方法。
再回溯到
最后回溯到
到此为止,
我们可以发现这其实是一个一直递归回溯判断的过程
然后判断是不是左偏的话,我们利用了性质2
所以 很自然地写出来了
#define Lc t[x].lc#define Rc t[x].rcint fa[MAXN]; int Merge(int x,int y) { if(!x||!y) return x+y; //如果有一个节点是空节点,那么merge之后的根就是x+y if(t[y]<t[x]) swap(x,y); if(t[x].val==t[y].val&&x>y) swap(x,y); //我们在上面的图中,并没有体现出上面的swap //因为是小根堆,所以肯定是大的并到小的上面去,所以有if(t[y]<t[x]) swap(x,y); //可能写成t[x]>t[y] 好理解一点,但是我只重载了 < ,所以就这么写了 //第二个if就是让编号大的并到编号小的上去 Rc=Merge(Rc,y); //y一直和x的右子树合并 fa[Rc]=x; //合并了,也不能忘了自己的爸爸是谁 if(t[Lc].dis<t[Rc].dis) //利用性质2判断是否左偏 swap(Lc,Rc); t[x].dis=t[Rc].dis+1;//利用性质3来更新dis return x;//返回合并后的根,方便回溯来维护左偏}
左偏树经常和并查集结合到一起,因为你要判断合并的点是不是在一棵左偏树里,但是这个并查集不要带路径压缩
也就是说
int Find(int x) { for(;fa[x];x=fa[x]); return x;}
到这里,
pop
比较好理解,没什么好说的,注意打一个不在树中的标记和把他清零就好了
贴一下代码
bool not_intree[MAXN];void pop(int x) { not_intree[x]=true; fa[Lc]=fa[Rc]=0; Merge(Lc,Rc); return;}
insert
可以用左偏树来做的题目
简单题
- 洛谷 P3377 【模板】左偏树(可并堆)
直接贴代码了
#include<cstdio>#include<cstring>#include<algorithm>using namespace std;typedef long long ll;template<typename T>void input(T &x) { x=0; T a=1; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') a=-1; for(;c>='0'&&c<='9';c=getchar()) x=x*10+c-'0'; x*=a; return;}#define MAXN 100010struct Leftist_Tree { int lc,rc,val,dis; bool operator < (const Leftist_Tree &q)const { return val<q.val; } bool operator == (const Leftist_Tree &q)const { return val==q.val; }};Leftist_Tree t[MAXN];int fa[MAXN];#define Lc t[x].lc#define Rc t[x].rcint Merge(int x,int y) { if(!x||!y) return x+y; if(t[y]<t[x]) swap(x,y); if(t[x]==t[y]&&x>y) swap(x,y); fa[Rc=Merge(Rc,y)]=x; if(t[Lc].dis<t[Rc].dis) swap(Lc,Rc); t[x].dis=t[Rc].dis+1; return x;}bool not_intree[MAXN];void pop(int x) { not_intree[x]=true; fa[Lc]=fa[Rc]=0; Merge(Lc,Rc); return;}#undef Lc#undef Rcint Find(int x) { for(;fa[x];x=fa[x]); return x;}int main() { int n,m; input(n),input(m); for(int i=1;i<=n;i++) input(t[i].val); t[0].dis=1; for(int op,x,y;m;m--) { input(op); if(op==1) { input(x),input(y); if(x==y) continue; if(not_intree[x]||not_intree[y]) continue; Merge(Find(x),Find(y)); } else { input(x); if(not_intree[x]) puts("-1"); else { printf("%d\n",t[y=Find(x)].val), pop(y); } } } return 0;}
- 洛谷 P1456 Monkey King
这道题需要大根堆,在我的代码里,只需要改重载运算符就可以了
建议大家也这么写
这道题在
#include<cstdio>#include<cstring>#include<algorithm>using namespace std;typedef long long ll;template<typename T>void input(T &x) { x=0; T a=1; register char c=getchar(); for(;c<48||c>57;c=getchar()) if(c==45) a=-1; for(;c>=48&&c<=57;c=getchar()) x=x*10+c-48; x*=a; return;}#define MAXN 100010struct Leftist_Tree { int lc,rc,val,dis; bool operator < (const Leftist_Tree &q)const { return val>q.val; } bool operator == (const Leftist_Tree &q)const { return val==q.val; }};Leftist_Tree t[MAXN];int fa[MAXN];int Find(int x) { for(;fa[x];x=fa[x]); return x;}#define Lc t[x].lc#define Rc t[x].rcint Merge(int x,int y) { if(!x||!y) return x+y; if(t[y]<t[x]) swap(x,y); if(t[x]==t[y]&&x>y) swap(x,y); fa[Rc=Merge(Rc,y)]=x; if(t[Lc].dis<t[Rc].dis) swap(Lc,Rc); t[x].dis=t[Rc].dis+1; return x;}int pop(int x) { fa[Lc]=fa[Rc]=0; int ans=Merge(Lc,Rc); Lc=0,Rc=0; return ans;}#undef Lc#undef Rcint n;void Clear() { for(int i=0;i<=n;i++) { fa[i]=0; t[i].lc=t[i].rc=t[i].dis=0; } t[0].dis=1; return;}int main() { while(~scanf("%d",&n)) { Clear(); for(int i=1;i<=n;i++) input(t[i].val); int m; input(m); for(int x,y;m;m--) { input(x),input(y); x=Find(x),y=Find(y); if(x==y) puts("-1"); else { t[x].val>>=1,t[y].val>>=1; int rt1=Merge(x,y), rt2=Merge(pop(x),pop(y)); printf("%d\n",t[Merge(rt1,rt2)].val); } } } return 0;}
好像 HDU 也有这道题
难题
bzoj 2333: [SCOI2011]棘手的操作
bzoj 2809: [Apio2012]dispatching
写不来的。。
- [StudyNotes] 左偏树
- Asp.Net Design Pattern Studynotes -- Part1
- 左偏树
- 左偏树
- 左偏树
- 左偏树
- 左偏树
- 左偏树
- 左偏树
- 左偏树
- 左偏树
- 左偏树
- 左偏树
- 左偏树
- 左偏树
- 左偏树
- 左偏树
- 左偏树
- Spring Boot ,Spring Security的Filter在Tomcat正常,部署在Weblogic 12c Filter顺序出错,导致拦截权限问题
- 项目
- 一些C++的小程序(一)
- tomcat域名配置
- Spring MVC 集成Velocity
- [StudyNotes] 左偏树
- python2中一些模块到python3中名称的变化
- maven 打包可执行jar
- 使用window.location.hash解决ajax刷新和导航问题
- AutoMapper官方文档(三)【约定】
- 课后作业之邮箱类
- MySQL学习笔记
- 如果其他类型更适合,则尽量避免使用字符串
- MySQL数据库连接数过多的常见原因