左偏树(可合并堆)

【算法简介】

顾名思义,这个数据结构向左偏,而且主要的性质是可以合并,而且是一个有序的堆

定义外节点是有一个儿子是空的的节点,每个点的dis定义为到最近的外节点经过的边的数量

我们让每个节点的左儿子的dis>=右儿子的dis,然后操作尽量从右侧开始,通过这种方式来保证其优秀的时间效率

合并操作就是每次让右侧儿子和另一棵树进行合并,合并的过程中注意满足有序的性质,还有左偏的性质就行

还有一个删除操作,左偏树不支持删除某个值得点的操作,只能删除某个已知位置的点,一般常用的操作是删除根节点

【例题】P1552 [APIO2012]派遣

【题意】

一个点有两个权值,工资和领导力,你有m元预算用来支付工资,请在一个树上任选一个节点作为leader,选择其子树内的若干节点(自己也可以选),保证能支付的起工资,并最大化人员个数*leader的领导力

【分析】

贪心的考虑,选定了一个leader后,一定要尽量选工资便宜的员工,所以这个问题变成了一个求子树最多能去多少个,使得工资和不超过m

所以我们可以在做dfs的时候,向上合并,初始的时候把每个点加成只有自己的堆,然后向上回溯的过程中合并堆,每次访问完子树,合并完堆后,计算自己作leader的ans,弹出自己队列中工资高的,直到总工资不超过m

【代码】

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
typedef long long ll;
int n,m;
ll c[maxn],l[maxn];
int head[maxn],cnt;
struct edge
{
	int to,nxt;
}e[maxn];
void add(int x,int y)
{
	e[++cnt].to=y; e[cnt].nxt=head[x]; head[x]=cnt;
}
int root,b;
struct tree
{
	int ls,rs,fa;
	ll val,dis;
}tr[maxn];
ll siz[maxn],valu[maxn];
int merge(int x,int y)
{
	if(!x || !y) return x+y;
	if(tr[x].val<tr[y].val) swap(x,y);
	tr[x].rs=merge(tr[x].rs,y);
	if(tr[tr[x].rs].dis>tr[tr[x].ls].dis) swap(tr[x].ls,tr[x].rs);
	if(!tr[x].rs || !tr[x].ls) tr[x].dis=0;
	else tr[x].dis=tr[tr[x].rs].dis+1;
	return x;
}
ll ans;
int del(int x)
{
	int l=tr[x].ls,r=tr[x].rs;
	tr[l].fa=l; tr[r].fa=r;
	tr[x].ls=tr[x].rs=tr[x].dis=0;
	return merge(l,r);
}
int id[maxn];
void dfs(int u)
{
	siz[u]=1; valu[u]=c[u];
	for(int i=head[u];i;i=e[i].nxt)
	{
		int to=e[i].to;
		dfs(to);
		id[u]=merge(id[u],id[to]);
		siz[u]+=siz[to]; valu[u]+=valu[to];
	}
	while(valu[u]>m)
	{
		siz[u]--; valu[u]-=tr[id[u]].val;
		id[u]=del(id[u]);
	}
	ans=max(ans,l[u]*siz[u]);
}
int main()
{
	freopen("dispatching.in","r",stdin);
	freopen("dispatching.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d%d",&b,&c[i],&l[i]);
		if(!b) root=i;
		else
			add(b,i);
		id[i]=i;
	}
	tr[0].dis=-1;
	for(int i=1;i<=n;i++) tr[i].fa=i,tr[i].val=c[i];
	dfs(root);
	printf("%lld\n",ans);
	return 0;
}

【练习1】P3261 [JLOI2015]城池攻占

【题意】

题意好麻烦

【分析】

开始把士兵加到各个节点的队列中,类似例题,做dfs的过程中,合并堆,每次删除hp<0的士兵,并记录death和num的贡献

每个节点的修改可以记录在标记上,注意及时下传即可

【代码】

#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+5;
typedef long long ll;
int n,m;
int head[maxn],cnt,op[maxn],v[maxn],c[maxn];
int s[maxn],dep[maxn],fa[maxn];
ll num[maxn],death[maxn],h[maxn];
struct edge
{
	int to,nxt;
}e[maxn];
struct tree
{
	int ls,rs,fa;
	ll val,dis,mtag,atag;
}tr[maxn];
void add(int x,int y)
{
	e[++cnt].to=y; e[cnt].nxt=head[x]; head[x]=cnt;
}
int root[maxn];
void pushdown(int x)
{
	ll mt=tr[x].mtag,at=tr[x].atag;
	if(tr[x].ls)
	{
		tr[tr[x].ls].mtag*=mt; tr[tr[x].ls].atag*=mt;
		tr[tr[x].ls].atag+=at; tr[tr[x].ls].val=tr[tr[x].ls].val*mt+at;
	}
	if(tr[x].rs)
	{
		tr[tr[x].rs].mtag*=mt; tr[tr[x].rs].atag*=mt;
		tr[tr[x].rs].atag+=at; tr[tr[x].rs].val=tr[tr[x].rs].val*mt+at;
	}
	tr[x].mtag=1; tr[x].atag=0;
}
int merge(int x,int y)
{
	if(!x || !y) return x+y;
	pushdown(x),pushdown(y);
	if(tr[x].val>tr[y].val) swap(x,y);
	tr[x].rs=merge(tr[x].rs,y);
	if(tr[tr[x].ls].dis<tr[tr[x].rs].dis) swap(tr[x].ls,tr[x].rs);
	tr[x].dis=tr[tr[x].rs].dis+1;
	return x;
}
void dfs(int u)
{
	dep[u]=dep[fa[u]]+1;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int to=e[i].to;
		dfs(to);
		root[u]=merge(root[u],root[to]);
	}
	while(tr[root[u]].val<h[u] && root[u])
	{
		pushdown(root[u]);
		death[u]++;
		num[root[u]]=dep[c[root[u]]]-dep[u];
		root[u]=merge(tr[root[u]].ls,tr[root[u]].rs);
	}
	if(!op[u])
	{
		tr[root[u]].atag+=v[u];
		tr[root[u]].val+=v[u];
	}
	else
	{
		tr[root[u]].mtag*=v[u];
		tr[root[u]].atag*=v[u];
		tr[root[u]].val*=v[u];
	}
}
int main()
{
	freopen("city.in","r",stdin);
	freopen("city.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lld",&h[i]);
	int x;
	for(int i=2;i<=n;i++)
	{
		scanf("%d%d%lld",&fa[i],&op[i],&v[i]);
		add(fa[i],i);
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%lld%d",&tr[i].val,&c[i]);
		root[c[i]]=merge(root[c[i]],i);
		tr[i].mtag=1;
	}
	dfs(1);
	while(root[1])
	{
		pushdown(root[1]);
		num[root[1]]=dep[c[root[1]]];
		root[1]=merge(tr[root[1]].ls,tr[root[1]].rs);
	}
	for(int i=1;i<=n;i++) printf("%lld\n",death[i]);
	for(int i=1;i<=m;i++) printf("%lld\n",num[i]);
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值