左偏树学习笔记
左偏树
定义
一种树形结构,具有堆的性质。
对于一棵二叉树,定义 外节点 为子节点数小于两个的节点,定义一个节点的 \(\operatorname{dist}\) 为其到子树中最近的外节点经过的边的数量。空节点的 \(dist\) 为 \(0\)。
性质
-
左偏树是一颗二叉树,满足堆的性质,且满足左偏:即 \(\operatorname{dist}_{ls_u}\ge \operatorname{dist}_{rs_{u}}\)。
-
由 \(1\) 可得 \(\operatorname{dist}_u=\operatorname{dist}_{rs_u}+1\)。
-
左偏树的深度没有保证。一条向左的链也满足左偏。
基本操作
- 定义
struct node
{
int ls,rs;
int val,dist;
}t[N];
int fa[N];
#define rs(x) t[x].rs
#define ls(x) t[x].ls
- 新建节点
在一个数加入一个堆中会用到。
int newnode(int x,int val)
{
t[x].val=val;
ls(x)=rs(x)=0;
fa[x]=x;
}
- 合并两个堆
以小根堆为例。
为了满足小根堆的性质,选取值较小的那个根作为合并后堆节点的根节点,然后将这个根的做儿子作为合并后堆的左儿子,然后递归合并其右儿子和另一个堆,作为合并之后的堆的右儿子。为了满足左偏性质,合并后如果不满足左偏则交换两个儿子。
注意更新 \(\operatorname{dist}\) 以及 \(fa\)(后面的操作会用到)。
int merge(int x,int y)
{
if(!x||!y) return x|y;
if(t[x].val>t[y].val) sd swap(x,y);
rs(x)=merge(rs(x),y);
if(t[ls(x)].dist<t[rs(x)].dist) sd swap(ls(x),rs(x));
t[x].dist=t[rs(x)].dist+1;
fa[ls(x)]=fa[rs(x)]=fa[x]=x;
return x;
}
- 加入一个数
即一个只有一个节点的堆和另一个堆合并。
- 删除根
合并根的左右儿子。
void erase(int x)//堆删除x所在堆堆顶
{
t[x].val=-1;//标记为空节点
fa[ls(x)]=ls(x);
fa[rs(x)]=rs(x);
fa[x]=merge(ls(x),rs(x));
}
P3377 【模板】左偏树(可并堆)
Code
#include<bits/stdc++.h>
#define sd std::
//#define int long long
#define F(i,a,b) for(int i=(a);i<=(b);i++)
#define f(i,a,b) for(int i=(a);i>=(b);i--)
#define MIN(x,y) (x<y?x:y)
#define MAX(x,y) (x>y?x:y)
#define me(x,y) memset(x,y,sizeof x)
#define pii sd pair<int,int>
#define X first
#define Y second
#define Fr(a) for(auto it:a)
int read(){int w=1,c=0;char ch=getchar();for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;for(;ch>='0'&&ch<='9';ch=getchar()) c=(c<<3)+(c<<1)+ch-48;return w*c;}
void printt(int x){if(x>9) printt(x/10);putchar(x%10+48);}
void print(int x){if(x<0) putchar('-'),printt(-x);else printt(x);}
void put(int x){print(x);putchar('\n');}
void printk(int x){print(x);putchar(' ');}
const int N=2e5+10;
struct node
{
int ls,rs;
int val,dist;
}t[N];
int fa[N];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
#define rs(x) t[x].rs
#define ls(x) t[x].ls
int merge(int x,int y)
{
if(!x||!y) return x|y;
if(t[x].val>t[y].val) sd swap(x,y);
rs(x)=merge(rs(x),y);
if(t[ls(x)].dist<t[rs(x)].dist) sd swap(ls(x),rs(x));
t[x].dist=t[rs(x)].dist+1;
fa[ls(x)]=fa[rs(x)]=fa[x]=x;
return x;
}
void erase(int x)//堆删除x所在堆堆顶
{
t[x].val=-1;//标记为空节点
fa[ls(x)]=ls(x);
fa[rs(x)]=rs(x);
fa[x]=merge(ls(x),rs(x));
}
int n,m;
void solve()
{
n=read(),m=read();
F(i,1,n) fa[i]=i,t[i].val=read();
F(i,1,m)
{
int op=read(),x=read(),y;
if(op==1)
{
y=read();
z=find(y);
if(l!=r) fa[l]=fa[r]=merge(l,r);
}
else
{
if(!~t[x].val) put(-1);
else put(t[find(x)].val),erase(find(x));
}
}
}
int main()
{
int T=1;
// T=read();
while(T--) solve();
return 0;
}
练习题
#include<bits/stdc++.h>
#define sd std::
//#define int long long
#define F(i,a,b) for(int i=(a);i<=(b);i++)
#define f(i,a,b) for(int i=(a);i>=(b);i--)
#define MIN(x,y) (x<y?x:y)
#define MAX(x,y) (x>y?x:y)
#define me(x,y) memset(x,y,sizeof x)
#define pii sd pair<int,int>
#define X first
#define Y second
#define Fr(a) for(auto it:a)
#define dbg(x) sd cout<<#x<<":"<<x<<"\n"
int read(){int w=1,c=0;char ch=getchar();for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;for(;ch>='0'&&ch<='9';ch=getchar()) c=(c<<3)+(c<<1)+ch-48;return w*c;}
void printt(int x){if(x>9) printt(x/10);putchar(x%10+48);}
void print(int x){if(x<0) putchar('-'),printt(-x);else printt(x);}
void put(int x){print(x);putchar('\n');}
void printk(int x){print(x);putchar(' ');}
const int N=1e5+10;
int n,m;
struct node
{
int val,dist;
int ls,rs;
}t[N];
int fa[N];
#define ls(x) t[x].ls
#define rs(x) t[x].rs
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
int merge(int x,int y)
{
if(!x||!y) return x|y;
if(t[x].val<t[y].val) sd swap(x,y);
rs(x)=merge(rs(x),y);
if(t[ls(x)].dist<t[rs(x)].dist) sd swap(ls(x),rs(x));
t[x].dist=t[rs(x)].dist+1;
fa[ls(x)]=fa[rs(x)]=fa[x]=x;
return x;
}
int erase(int x)
{
t[x].val=-1;
fa[ls(x)]=ls(x);
fa[rs(x)]=rs(x);
return fa[x]=merge(ls(x),rs(x));
}
int weak(int x)
{
t[x].val>>=1;
int rt=merge(ls(x),rs(x));
t[x].ls=t[x].rs=t[x].dist=0;
return fa[rt]=fa[x]=merge(rt,x);
}
void solve()
{
F(i,1,n)
{
t[i].ls=t[i].rs=0;
}
F(i,1,n)
{
fa[i]=i;
t[i].val=read();
}
m=read();
F(i,1,m)
{
int x=read(),y=read();
x=find(x),y=find(y);
if(x==y)
{
put(-1);
continue;
}
fa[x]=fa[y]=merge(weak(x),weak(y));
put(t[find(x)].val);
}
}
int main()
{
int T=1;
// T=read();
while(scanf("%d",&n)!=EOF) solve();
return 0;
}
#include<bits/stdc++.h>
#define sd std::
//#define int long long
#define F(i,a,b) for(int i=(a);i<=(b);i++)
#define f(i,a,b) for(int i=(a);i>=(b);i--)
#define MIN(x,y) (x<y?x:y)
#define MAX(x,y) (x>y?x:y)
#define me(x,y) memset(x,y,sizeof x)
#define pii sd pair<int,int>
#define X first
#define Y second
#define Fr(a) for(auto it:a)
int read(){int w=1,c=0;char ch=getchar();for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;for(;ch>='0'&&ch<='9';ch=getchar()) c=(c<<3)+(c<<1)+ch-48;return w*c;}
void printt(int x){if(x>9) printt(x/10);putchar(x%10+48);}
void print(int x){if(x<0) putchar('-'),printt(-x);else printt(x);}
void put(int x){print(x);putchar('\n');}
void printk(int x){print(x);putchar(' ');}
const int N=2e6+10;
int n,m;
struct node
{
int ls,rs;
int val,dist;
}t[N];
int fa[N];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
#define ls(x) t[x].ls
#define rs(x) t[x].rs
int merge(int x,int y)
{
if(!x||!y) return x|y;
if(t[x].val>t[y].val) sd swap(x,y);
rs(x)=merge(rs(x),y);
if(t[ls(x)].dist<t[rs(x)].dist) sd swap(ls(x),rs(x));
t[x].dist=t[rs(x)].dist+1;
fa[x]=fa[ls(x)]=fa[rs(x)]=x;
return x;
}
void erase(int x)
{
t[x].val=-1;
fa[rs(x)]=rs(x);
fa[ls(x)]=ls(x);
fa[x]=merge(ls(x),rs(x));
}
void solve()
{
n=read();
F(i,1,n)
{
fa[i]=i;
t[i].val=read();
}
m=read();
while(m--)
{
char op[2];
int x,y;
scanf("%s",op);
if(op[0]=='M')
{
x=read(),y=read();
if(!~t[x].val||!~t[y].val) continue;
x=find(x),y=find(y);
if(x!=y)
{
fa[x]=fa[y]=merge(x,y);
}
}
else
{
x=read();
if(!~t[x].val)
{
put(0);
continue;
}
x=find(x);
put(t[x].val);
erase(x);
}
}
}
int main()
{
int T=1;
// T=read();
while(T--) solve();
return 0;
}
进阶操作
- 删除任意数(编号)
同样是合并这个数的左右儿子,但有可能影响到祖先,所以考虑标记上传。
具体来讲,如果有一处 \(dist_u\not =dist_{rs_u}+1\),就重新赋值并上传。
注意上传的代码是 pushup(x),所以不能用路径压缩了。
void pushup(int x)
{
if(!x) return;
if(t[x].dist!=t[rs(x)].dist+1)
{
t[x].dist=t[rs(x)].dist+1;
pushup(fa[x]);
}
}
此时合并操作需要重新写:
int merge(int x,int y)
{
if(!x||!y) return x|y;
if(t[x].val>t[y].val) sd swap(x,y);
rs(x)=merge(rs(x),y);
if(t[ls(x)].dist<t[rs(x)].dist) sd swap(ls(x),rs(x));
pushup(x);
fa[ls(x)]=fa[rs(x)]=x;//注意这里的 fa 不再是它的根结点,而是它的直接父亲
return x;
}
删除操作:
int pop(int x)
{
return merge(ls(x),rs(x));
}
- 对一个堆整体修改
打懒标记。类似线段树,查询和合并的时候下传。
比如 [JLOI2015]城池攻占 的下传,注意先乘后加。
void pushdown(int x)
{
if(add(x)==0&&mul(x)==1) return;
if(ls(x))
{
val(l(x))*=mul(x);
val(l(x))+=add(x);
mul(l(x))*=mul(x);
add(l(x))*=mul(x);
add(l(x))+=add(x);
}
if(rs(x))
{
val(r(x))*=mul(x);
val(r(x))+=add(x);
mul(r(x))*=mul(x);
add(r(x))*=mul(x);
add(r(x))+=add(x);
}
add(x)=0,mul(x)=1;
}
[JLOI2015]城池攻占
明天一定补。
练习题
明天一定补。
P1552 [APIO2012] 派遣
P3273 [SCOI2011]棘手的操作
P4331 [BalticOI 2004]Sequence 数字序列

浙公网安备 33010602011771号