左偏树

左偏树

什么是左偏树

左偏树即是同时具有左偏性和堆性质的树。堆性质保证我们可以容易地弹出堆顶元素,插入新的元素,得到堆顶元素,而左偏性保证了我们可以在 O ( l o g ( n ) ) O(log(n)) O(log(n))的复杂度内合并两个堆

左偏树有什么作用

在维护一些树状关系时,我们常常需要求出树上一些点的的前数项和以满足其项数小于某个值,或者其和小于某个值,这样比较直接的想法就是求出每个节点的子节点权值的排序。但实际上,由于所有子树上的权值最后会进入其父节点的权值集合,所以较好的方法是对每个节点维护一棵左偏树,求其父节点时,则将父节点的所有左偏树合并,即可维护父节点的前几项权值

左偏树模板

int Merge(int A,int B){
    if (!A||!B) return A+B;
    if (key[A]<key[B]) swap(A,B);
    rs[A]=Merge(rs[A],B);
    if (dis[ls[A]]<dis[rs[A]]) swap(ls[A],rs[A]);
    dis[A]=dis[rs[A]]+1;
    return A;
}
int Delete(int A){
    return Merge(ls[A],rs[A]);
}

例题1

Question

在一个忍者的帮派里,一些忍者们被选中派遣给顾客,然后依据自己的工作获取报偿。在这个帮派里,有一名忍者被称之为Master。除了 Master以外,每名忍者都有且仅有一个上级。为保密,同时增强忍者们的领导力,所有与他们工作相关的指令总是由上级发送给他的直接下属,而不允许通过其他的方式发送。现在你要招募一批忍者,并把它们派遣给顾客。你需要为每个被派遣的忍者 支付一定的薪水,同时使得支付的薪水总额不超过你的预算。另外,为了发送指令,你需要选择一名忍者作为管理者,要求这个管理者可以向所有被派遣的忍者 发送指令,在发送指令时,任何忍者(不管是否被派遣)都可以作为消息的传递 人。管理者自己可以被派遣,也可以不被派遣。当然,如果管理者没有被排遣,就不需要支付管理者的薪水。你的目标是在预算内使顾客的满意度最大。这里定义顾客的满意度为派遣的忍者总数乘以管理者的领导力水平,其中每个忍者的领导力水平也是一定的。写一个程序,给定每一个忍者ii的上级 Bi,薪水Ci,领导力Li,以及支付给忍者们的薪水总预算 M,输出在预算内满足上述要求时顾客满意度的最大值。

Input

从标准输入读入数据。
第一行包含两个整数 NM,其中 N表示忍者的个数,M表示薪水的总预 算。
接下来 N行描述忍者们的上级、薪水以及领导力。其中的第 i 行包含三个整 Bi , C i , L i分别表示第i个忍者的上级,薪水以及领导力。Master满足B i = 0, 并且每一个忍者的老板的编号一定小于自己的编号 B i &lt; i B_i&lt;i Bi<i

Output

输出一个数,表示在预算内顾客的满意度的最大值。

Solution

对每个节点的子节点的工资维护一个左偏树,使得树的权值之和总是小于M,对不同的节点都维护出根节点及其子树最多可选的点的乘积,最后取乘积的最大值即得出答案

AC代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int size=1e5+5;
int ls[size],rs[size];
long long sz[size],L[size],C[size];
int dis[size];
int n,m;
int to[size],head[size],nxt[size];
long long sum[size];
int tot=0;
void addedge(int u,int v)
{
	nxt[tot]=head[u];
	to[tot]=v;
	head[u]=tot++;
}
int merge(int A,int B)
{
	if(!A||!B) return A+B;
	if(C[A]<C[B]) swap(A,B);
	rs[A]=merge(rs[A],B);
	if(dis[ls[A]]<dis[rs[A]]) swap(ls[A],rs[A]);
	dis[A]=dis[rs[A]]+1;
	return A; 
}
int Delete(int A)
{
	return merge(ls[A],rs[A]);	
}
long long ans=0;
int dfs(int v)
{
	int A=v,B;
	sum[v]=C[v];sz[A]=1;
	int u;
	for(int i=head[v];i!=-1;i=nxt[i])
	{
		u=to[i];
		B=dfs(u);
		A=merge(A,B);
		sum[v]+=sum[u],sz[v]+=sz[u];
	}
	while(sum[v]>m)
	{
		sum[v]-=C[A];sz[v]--;
		A=Delete(A);
	}
	ans=max(ans,L[v]*sz[v]);
	return A;
}
int main()
{
	scanf("%d%d",&n,&m);
	memset(dis,0,sizeof(dis));
	memset(head,-1,sizeof(head));
	int root;
	for(int i=1;i<=n;i++)
	 {
		int p;
		scanf("%d",&p); 
		if(!p) root=i;
		else  addedge(p,i);
		scanf("%lld%lld",&C[i],&L[i]);
	}
	dfs(root);
	printf("%lld\n",ans);
}
 

例题2

Question

Once in a forest, there lived N aggressive monkeys. At the beginning, they each does things in its own way and none of them knows each other. But monkeys can’t avoid quarrelling, and it only happens between two monkeys who does not know each other. And when it happens, both the two monkeys will invite the strongest friend of them, and duel. Of course, after the duel, the two monkeys and all of there friends knows each other, and the quarrel above will no longer happens between these monkeys even if they have ever conflicted.

Assume that every money has a strongness value, which will be reduced to only half of the original after a duel(that is, 10 will be reduced to 5 and 5 will be reduced to 2).

And we also assume that every monkey knows himself. That is, when he is the strongest one in all of his friends, he himself will go to duel.

Input

There are several test cases, and each case consists of two parts.

First part: The first line contains an integer N(N<=100,000), which indicates the number of monkeys. And then N lines follows. There is one number on each line, indicating the strongness value of ith monkey(<=32768).

Second part: The first line contains an integer M(M<=100,000), which indicates there are M conflicts happened. And then M lines follows, each line of which contains two integers x and y, indicating that there is a conflict between the Xth monkey and Yth.

Output

For each of the conflict, output -1 if the two monkeys know each other, otherwise output the strongness value of the strongest monkey in all friends of them after the duel.

题目大意

给出一些节点,其拥有确定的权值,每次给出一对节点,如两个节点之间原本就在同一个群体中则输出-1,否则两个节点原本所属的群体中的最大权值节点的权值减半,并将两个节点原本所属的群体合并成一个新群体,并输出新群体拥有最大权值的点的权值

solution

通过并查集来维护两个节点是否属于同一集体,再对每个集体维护一个左偏树,每次合并时就删取两个群体中权值最大的点使其权值减半后再重新插入,合并成一个新的堆之后再输出新的堆顶元素的权值

AC代码

#include<cstdio>
#include<vector>
#include<cstdio>
#include<cstring>
using namespace std;
const int size=1e5+5;
int ls[size],rs[size];
int val[size];
int fa[size];
int dis[size];
int merge(int A,int B)
{
	if(!A||!B) return A+B;
	if(val[A]<val[B]) swap(A,B);
	rs[A]=merge(rs[A],B);
	if(dis[ls[A]]<dis[rs[A]]) swap(ls[A],rs[A]);
	dis[A]=dis[rs[A]]+1;
	return A; 
}
int find(int x)
{
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
int Delete(int A)
{
	return merge(ls[A],rs[A]);
}
int modify(int x)
{
	if(ls[x]==0&&rs[x]==0)
	{
		val[x]/=2;
		return x;
	}
	val[x]/=2;
	int newroot=Delete(x);
	fa[x]=x;
	fa[newroot]=newroot;
	ls[x]=0,rs[x]=0;
	int nxtroot=merge(newroot,x);
	fa[x]=nxtroot;fa[newroot]=nxtroot;
	return nxtroot;
}
int main()
{
	int n,m;
	while(~scanf("%d",&n))
	{
		memset(ls,0,sizeof(ls));
		memset(rs,0,sizeof(rs));
		memset(dis,0,sizeof(dis));
		for(int i=1;i<=n;i++) scanf("%d",&val[i]),fa[i]=i;
		scanf("%d",&m);
		for(int i=1;i<=m;i++)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			int fax=find(x),fay=find(y);
			if(fax==fay)
			{
				puts("-1"); 
				continue;
			}
			int xplus=modify(fax),yplus=modify(fay);
			int newroot=merge(xplus,yplus);
			fa[xplus]=newroot;
			fa[yplus]=newroot;
			printf("%d\n",val[newroot]);
		}
	}
}
	
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值