左偏树学习笔记

左偏树

定义

一种树形结构,具有堆的性质。

对于一棵二叉树,定义 外节点 为子节点数小于两个的节点,定义一个节点的 \(\operatorname{dist}\) 为其到子树中最近的外节点经过的边的数量。空节点的 \(dist\)\(0\)

性质

  1. 左偏树是一颗二叉树,满足堆的性质,且满足左偏:即 \(\operatorname{dist}_{ls_u}\ge \operatorname{dist}_{rs_{u}}\)

  2. \(1\) 可得 \(\operatorname{dist}_u=\operatorname{dist}_{rs_u}+1\)

  3. 左偏树的深度没有保证。一条向左的链也满足左偏。

基本操作

  • 定义
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;
}

练习题

  1. P1456 Monkey King
#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;
}
  1. P2713 罗马游戏
#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 数字序列

posted @ 2024-12-20 20:20  _E_M_T  阅读(35)  评论(0)    收藏  举报