左偏树

左偏树

可以发现左偏树的别名叫可并堆,就是可合并的堆。于是我们的主要操作便是合并。

我们定义外节点为左儿子或右儿子至少有一个为空的节点。我们定义外节点的 \(dist\)\(1\),其余点的 \(dist\) 为到其子树内最近的外节点的距离加上 \(1\)

左偏树是一棵二叉树,首先其具有堆的性质,并且它是左偏的,每个节点的左儿子的 \(dist\) 都不小于右儿子的 \(dist\)

然后说一下 \(dist\) 的作用,首先是让树左偏,然后左偏又可以保证合并的复杂度。大概原因是每次合并右边,而右边的 \(dist\) 级别是 \(\log\) 的,每次合并少一层,所以复杂度是 \(\log\) 的。

注意:左偏树的深度没有保证

考虑类似于线段树合并的过程,合并两棵树时,比方说一个节点是 \(x\),另一个节点是 \(y\)。如果这俩有一个是 \(0\),那么直接返回另一边,这个过程与其他带合并的东西是一样的。

然后就是因为是小根堆,所以我们把权值更小的那个放在上面,另一个合并到右儿子即可。

合并完了之后,我们就要维护左偏的性质,考虑如果不满足,则交换左右儿子即可。最后维护一下 \(d\) 即可。

部分代码:

int merge(int x,int y){
	if(x==0||y==0)return x+y;
	if(a[x]>a[y])swap(x,y);
	tr[x].r=merge(tr[x].r,y);
	if(tr[tr[x].l].d<tr[tr[x].r].d)swap(tr[x].l,tr[x].r);
	tr[x].d=tr[tr[x].r].d+1;
	return x;
}

然后就是删除最小数,显然因为维护的是小根堆,所以删除的一定是堆顶,于是我们合并堆顶的两个儿子即可。

最后就是一些细节,拿一个数组记录一下这个数被删过没有,用来处理一些特殊情况,例如输出 -1

代码:

#include<bits/stdc++.h>
#define int long long
#define N 100005
using namespace std;
int n,m,fa[N];
bool st[N];
struct node{
	int id,val;
	bool operator>(const node &t)const{
		if(val==t.val)return id>t.id;
		return val>t.val;
	}
}a[N];
struct tree{
	int l,r,d;
}tr[N];
int find(int x){
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
int merge(int x,int y){
	if(x==0||y==0)return x+y;
	if(a[x]>a[y])swap(x,y);
	tr[x].r=merge(tr[x].r,y);
	if(tr[tr[x].l].d<tr[tr[x].r].d)swap(tr[x].l,tr[x].r);
	tr[x].d=tr[tr[x].r].d+1;
	return x;
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i].val;
		fa[i]=a[i].id=i;
	}
	while(m--){
		int op,x,y;
		cin>>op>>x;
		if(op==1){
			cin>>y;
			if(st[x]||st[y])continue;
			int fx=find(x),fy=find(y);
			if(fx!=fy)fa[fx]=fa[fy]=merge(fx,fy);
		}
		else{
			if(st[x])cout<<"-1\n";
			else{
				int fx=find(x);
				cout<<a[fx].val<<'\n';
				st[fx]=1;
				fa[tr[fx].l]=fa[tr[fx].r]=fa[fx]=merge(tr[fx].l,tr[fx].r);
				tr[fx].l=tr[fx].r=tr[fx].d=0;
			}
		}
	}
	return 0;
}

派遣

首先我们假设确定了选择的根节点,那么我们显然要优先选择花费更少的点去产生贡献。所以,我们根据花费建出一个大根可并堆,然后依次删除根直到堆被删空或者剩下的点的花费和不超过 \(m\)

接下来每次合并父亲上的左偏树与儿子上的左偏树,类似一个动态规划的过程,然后就做完了。

左偏树的合并与删除堆顶都在上一道题有所提及,这里就不过多赘述了。

代码:

#include<bits/stdc++.h>
#define int long long
#define N 100005
#define M 200005
using namespace std;
int n,m,c[N],p[N],rt[N],res;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
	e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
struct node{
	int l,r,d,cost,sum,siz;
}tr[N];
void pushup(int u){
	tr[u].sum=tr[tr[u].l].sum+tr[tr[u].r].sum+tr[u].cost;
	tr[u].siz=tr[tr[u].l].siz+tr[tr[u].r].siz+1;
}
int merge(int x,int y){
	if(!x||!y)return x+y;
	if(tr[x].cost<tr[y].cost)swap(x,y);
	tr[x].r=merge(tr[x].r,y);
	if(tr[tr[x].l].d<tr[tr[x].r].d)swap(tr[x].l,tr[x].r);
	tr[x].d=tr[tr[x].r].d+1;
	pushup(x);
	return x;
}
int split(int x){
	return merge(tr[x].l,tr[x].r);
}
void dfs(int u,int fa){
	tr[u]={0,0,0,c[u],c[u],1};
	rt[u]=u;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa)continue;
		dfs(j,u);
		rt[u]=merge(rt[u],rt[j]);
	}
	while(tr[rt[u]].sum>m&&tr[rt[u]].siz!=0){
		rt[u]=split(rt[u]);
	}
	res=max(res,tr[rt[u]].siz*p[u]);
}
signed main(){
	cin>>n>>m;
	memset(h,-1,sizeof h);
	for(int i=1;i<=n;i++){
		int fa;
		cin>>fa>>c[i]>>p[i];
		add(fa,i);add(i,fa);
	}
	dfs(1,0);
	cout<<res;
}
posted @ 2024-09-09 20:26  zxh923  阅读(1)  评论(0编辑  收藏  举报