ybtAu「高级数据结构」第1章 左偏树

A. 【例题1】派遣

显然有贪心:对于每一个点,要选取 \(C_i\) 最小的那些点。
给每个节点开一个大根堆,维护子树里节点的 \(C_i\)。如果子树 \(C_i\) 之和大于 \(M\),那么不断弹节点直到 \(C_i\) 之和不超过 \(M\),之后把该节点的堆合并到父亲。

#include <iostream>
#define N 200005
#define int long long
int n,m,ans,b[N],c[N],l[N],hed[N],tal[N],nxt[N],cnte,rt[N],siz[N],sum[N];
void de(int u,int v) {if(u&&v) tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
namespace Heap
{
	int a[N],d[N],fa[N],ls[N],rs[N],idx;
	int nd(int x) {int nx=++idx;a[nx]=sum[nx]=x,d[nx]=-1,siz[nx]=1;return nx;}
	int mg(int x,int y)
	{
		if(!x||!y) return x|y;
		if(a[x]<a[y]) std::swap(x,y);
		rs[x]=mg(rs[x],y);
		if(d[rs[x]]>d[ls[x]]) std::swap(d[rs[x]],d[ls[x]]);
		d[x]=d[rs[x]]+1,siz[x]=siz[ls[x]]+siz[rs[x]]+1,sum[x]=sum[ls[x]]+sum[rs[x]]+a[x];
		return x;
	}
	void pop(int x) {rt[x]=mg(ls[rt[x]],rs[rt[x]]);}
};
void dfs(int x)
{
	rt[x]=Heap::nd(c[x]);
	for(int i=hed[x];i;i=nxt[i]) dfs(tal[i]),rt[x]=Heap::mg(rt[x],rt[tal[i]]);
	while(sum[rt[x]]>m) Heap::pop(x);
	ans=std::max(ans,l[x]*siz[rt[x]]);
}
signed main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n>>m;
	for(int i=1;i<=n;i++) std::cin>>b[i]>>c[i]>>l[i],de(b[i],i);
	for(int i=1;i<=n;i++) if(!b[i]) dfs(i);
	std::cout<<ans;
}

B. 城池攻占

在每个城池开一个小根堆,存储攻打这个城池的骑士。
处理到这个节点时,不断弹堆顶直到堆顶的战斗力大于等于该节点生命值。之后给堆打一个 tag,合并到父亲。
注意处理完根节点时要把堆弹空。
左偏树的 tag 与线段树类似,在合并两堆时要先 pushdown

#include <iostream>
#define int long long
#define N 300005
int n,m,h[N],f[N],a[N],v[N],s[N],c[N],rt[N],dep[N],cnt[N],ans[N];
namespace Heap
{
	int val[N],tg1[N],tg2[N],d[N],fa[N],ls[N],rs[N];
	void nd(int x) {val[x]=s[x],d[x]=-1,tg1[x]=0,tg2[x]=1;}
	void mt(int x,int t1,int t2) {if(x) val[x]=val[x]*t2+t1,tg1[x]=tg1[x]*t2+t1,tg2[x]*=t2;}
	void pd(int x) {mt(ls[x],tg1[x],tg2[x]),mt(rs[x],tg1[x],tg2[x]),tg1[x]=0,tg2[x]=1;}
	int mg(int x,int y)
	{
		if(!x||!y) return x|y;
		pd(x),pd(y);
		if(val[x]>val[y]) std::swap(x,y);
		rs[x]=mg(rs[x],y);
		if(d[rs[x]]>d[ls[x]]) std::swap(ls[x],rs[x]);
		d[x]=d[rs[x]]+1;
		return x;
	}
	void pop(int x) {pd(rt[x]),rt[x]=mg(ls[rt[x]],rs[rt[x]]);}
};
signed main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n>>m;
	for(int i=1;i<=n;i++) std::cin>>h[i];
	for(int i=2;i<=n;i++) std::cin>>f[i]>>a[i]>>v[i];
	for(int i=1;i<=n;i++) dep[i]=dep[f[i]]+1;
	for(int i=1;i<=m;i++) std::cin>>s[i]>>c[i],Heap::nd(i),rt[c[i]]=Heap::mg(rt[c[i]],i);
	for(int i=n;i>=1;i--)
	{
		while(rt[i]&&Heap::val[rt[i]]<h[i]) ans[rt[i]]=dep[c[rt[i]]]-dep[i],Heap::pop(i),cnt[i]++;
		if(a[i]==0) Heap::mt(rt[i],v[i],1);
		if(a[i]==1) Heap::mt(rt[i],0,v[i]);
		rt[f[i]]=Heap::mg(rt[f[i]],rt[i]);
	}
	while(rt[0]) ans[rt[0]]=dep[c[rt[0]]],Heap::pop(0);
	for(int i=1;i<=n;i++) std::cout<<cnt[i]<<'\n';
	for(int i=1;i<=m;i++) std::cout<<ans[i]<<'\n';
}

C. 求k短路

不会,详见 OI-wiki。

D. 递增序列

考虑弱化版:求出一个单调不降序列。
发现如果原序列是单调不降的,那么所求序列就是原序列;如果原序列是单调递减的,那么所求序列是 \(n\) 个该序列的中位数。
把这两条性质合并一下,如果原序列能划分成若干个单调递减区间,每段的中位数是单调递增的,那么所求序列在这些区间上的值就是每段的中位数。
于是有了一种做法:维护若干个段,每段的中位数单调递增,当新加入一个段时,如果其中位数大于最后一个段,那么把这一段加入;否则,不断把它和最后一个段合并,直到只剩下一个段,或者它的中位数大于最后一个段。
可以用左偏树维护每一段的中位数,即建出大根堆,保持堆内有 \(\lfloor\frac{len}{2}\rfloor\) 个元素,堆顶即中位数,合并时把新加入段的左偏树和最后一个段的左偏树合并即可。
现在回到原问题:单调递增序列。只需把原序列的每一个数减去下标即可。

#include <iostream>
#define N 1000005
#define int long long
int n,a[N],b[N],rt[N],li[N],lb[N],rb[N],f[N];
namespace Heap
{
	int val[N],d[N],fa[N],ls[N],rs[N],idx,siz[N];
	void nd(int x) {val[x]=a[x],d[x]=-1,siz[x]=1;}
	int mg(int x,int y)
	{
		if(!x||!y) return x|y;
		if(val[x]<val[y]) std::swap(x,y);
		rs[x]=mg(rs[x],y);
		if(d[rs[x]]>d[ls[x]]) std::swap(ls[x],rs[x]);
		d[x]=d[rs[x]]+1,siz[x]=siz[ls[x]]+siz[rs[x]]+1;
		return x;
	}
	void pop(int x) {rt[x]=mg(ls[rt[x]],rs[rt[x]]);}
};
signed main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n;
	for(int i=1;i<=n;i++) std::cin>>a[i],a[i]-=i;
	int len=0;
	for(int i=1;i<=n;i++)
	{
		Heap::nd(i),rt[++len]=i,lb[len]=rb[len]=i;
		while(len>1&&Heap::val[rt[len-1]]>Heap::val[rt[len]])
		{
			len--;
			rt[len]=Heap::mg(rt[len],rt[len+1]),rb[len]=rb[len+1];
			int sz=rb[len]-lb[len]+1;
			while(Heap::siz[rt[len]]*2>sz) Heap::pop(len);
		}
	}
	for(int i=1;i<=len;i++) for(int j=lb[i];j<=rb[i];j++) b[j]=Heap::val[rt[i]];
	int ans=0;
	for(int i=1;i<=n;i++) ans+=abs(a[i]-b[i]);
	std::cout<<ans;
}
posted @ 2025-06-27 17:24  整齐的艾萨克  阅读(7)  评论(0)    收藏  举报