洛谷 P3377 【模板】左偏树(可并堆)
作者:互联网
传送门
左偏树
政治老师告诉我们,要从以下方面思考问题:
- 是什么
- 为什么
- 怎么做
1.是什么:嗯……从字面看,是树(堆),是一个长得向左偏的二叉树(堆)。就长这样:
定义们:
外节点:当且仅当节点 i 的左子树或右子树为空时,节点被称作外节点。
距离:一个点的距离,被定义为它子树中离他最近的外节点到这个节点的距离。
性质们:
- 满足二叉堆的性质
- 左儿子的距离大于等于右儿子的距离(所以左偏)
- 节点的距离等于它的右子节点的距离加1(显然)
- N个节点的左偏树root节点的距离最多为log(N+1)-1(保证了效率)(证明?懒得写了嘿嘿嘿)
2.为什么(或者说能干啥):能实现两个堆之间的合并O(logN+logM) 。普通的堆需要把其中一个堆里的元素一个一个插入另一个堆里,是O(NlogM)的。还能实现删除、插入等基本操作。
3.怎么做:主要是合并merge操作。
假设合并两个小根堆A和B,比较堆顶大小决定堆顶是谁(假设B)。然后A堆跟B堆的右儿子合并,递归上述操作。期间需要确定一个点的祖宗,用到并查集的find操作,要路径压缩。
对于删除操作,就是相当于把堆顶的两个儿子的堆合并起来。
对于单点插入操作,就是把这一个点看做一个堆,与要插入的堆合并。
注意在删除操作时,因为进行了路径压缩,所以某些节点的fa是这个删除的点(不一定是儿子)。这是就应该把儿子的fa改成本身,把删除的这个节点的fa改成新堆顶。
AC代码
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 using namespace std; 5 const int maxn=100005; 6 int n,m,a[maxn],fa[maxn],vis[maxn]; 7 struct node{ 8 int rs,ls,dis; 9 }t[maxn]; 10 int find(int x){ 11 if(fa[x]==x) return x; 12 return fa[x]=find(fa[x]); 13 } 14 void pushup(int x){ 15 if(t[t[x].rs].dis>t[t[x].ls].dis) swap(t[x].rs,t[x].ls); 16 t[x].dis=t[t[x].rs].dis+1; 17 } 18 int merge(int x,int y){ 19 if(!x||!y) return x|y; 20 if(vis[x]||vis[y]) return vis[x]?x:y; 21 if(a[x]>a[y]||(a[x]==a[y]&&x>y)) swap(x,y); 22 t[x].rs=merge(t[x].rs,y); 23 fa[t[x].rs]=x; 24 pushup(x); 25 return x; 26 } 27 void goout(int x){ 28 fa[t[x].ls]=t[x].ls; 29 fa[t[x].rs]=t[x].rs; 30 int newtp=merge(t[x].ls,t[x].rs); 31 fa[x]=newtp; 32 } 33 int main(){ 34 cin>>n>>m; 35 for(int i=1;i<=n;i++) fa[i]=i; 36 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 37 for(int i=1;i<=m;i++){ 38 int id,x,y; 39 scanf("%d",&id); 40 if(id==1){ 41 scanf("%d%d",&x,&y); 42 if(vis[x]||vis[y]) continue; 43 x=find(x); 44 y=find(y); 45 if(x==y) continue; 46 merge(x,y); 47 }else{ 48 scanf("%d",&x); 49 if(vis[x]) printf("-1\n"); 50 else{ 51 int tp=find(x); 52 printf("%d\n",a[tp]); 53 goout(tp); 54 vis[tp]=1; 55 } 56 } 57 } 58 return 0; 59 }
标签:洛谷,rs,int,fa,ls,P3377,节点,左偏 来源: https://www.cnblogs.com/yinyuqin/p/14058893.html