BZOJ2809 [APIO2012]dispatching-左偏树-左偏树学习笔记

64 篇文章 0 订阅
6 篇文章 0 订阅

题目链接:右转进入题目

题目大意:自行参考原题

题解:

算法是不难想到的,主体是dfs一遍,对于第i个点为管理者的情况,先处理出以第i个点为根的子树中所有点为管理者的答案;(假设已经处理好了)

那么要怎么做呢?显然,为了不超过预算,我们要把i这个人和i的子树中的人放到一块去,然后排个序,贪心的选取能力值较小的,能选多少是多少

设选了sz个,那么以第i个人为管理者的答案就是sz*L[i]。

但这样还是不好处理。

这里用到了黄学长那篇里面BOI的sequence辣道题的一个思想:如何求中位数呢?很简单,把小于中位数的数字全部扔掉,那么堆首不就是中位数了么?

同理,我们注意到,如果一个点,在以i为管理者时不被派遣,那么在以father[i],father[father[i]]....为管理者时也不会被派遣。

更进一步的说,我们只想要一个有序数列中,前面的连续项,使得这些项的和不超过M。然后想知道这连续项到底有多少项。

换句话说,对于一个非降序列,我们从队尾一直delete,到数列中的和<=M即可,这时数列的长度就是sz。

被删除的数到之后的计算中也一定不会被选中,因此删除之后也不需要考虑什么啦!

有些时候我们处理以i个根的子树,要把结点i全部的son的序列合并成一个序列。

因此,要维护这样一个数据结构,它支持:

1,查询一个数列的最大值

2,删除一个数列的最大值

3,合并两个数列

4,询问数列中元素个数

哈!辣不就是可并堆嘛!

更准确的说,这里使用左偏树,它的合并、删除操作时O(lgn)的,查询时O(1)的。

左偏树是这样一个东西,它满足堆性质,即,每一个结点的值都要比他的子树中所有点的值要小(或大),由堆性质可知根节点就是最小值/最大值。

同时他还满足一个叫做左偏性质的东西。对于每个点rt,我们维护一个dist,满足:

当这个点没有right儿子时候,rt.dist=0,否则rt.dist=rt.right_child.dist+1。

这是个什么东东我也是一知半解%%%……

先说最重要的合并操作,伪代码描述如下(算法描述见黄学长《左偏树的特点及其应用》):

leftist *merge(leftist *A,leftist *B)//¼ÙÉèÊÇС¸ù¶Ñ 
{
	if(A==NULL) return B;
	if(B==NULL) return A;
	if(A->value>B->value) swap(A,B);
	A->right=merge(A->right,B);
	if(A->right->dist>A->left->right)
		swap(A->right,A->left);
	if(A->right==NULL) A->dist=0;
	else A->dist=A->right->dist+1;
//	push_up(A);
	return A;
}

但是更常用的时数组版本的。

int merge(int x,int y)
{
	if(!x||!y) return x+y;
	if(value[x]>value[y]) swap(x,y);
	right[x]=merge(right[x],y);
	if(dist[right[x]]>dist[left[x]])
		swap(right[x],left[x]);
	if(right[x]) dist[x]=dist[right[x]]+1;
	else dist[x]=0;
	return x;
}

删除操作非常简单:

void pop(leftist* &A)
{
	A=merge(A->right,A->left);
}
void pop(int &x)
{
	x=merge(right[x],left[x]);
}
这样的话,描述一下之前辣个算法:

dfs。如果当前这个点x是叶子结点,非常好处理对吧

如果不是,记root[x]为x这个点所在的堆的堆顶编号,初始化root[x]=x。

把所有root[son[x]]都合并起来再和root[x]合并(其实就是把x插入),如果当前堆的所有元素的和>M,就pop掉最大值,一直删除到sum<=M为止。

此时堆中元素个数sz[x]*L[x]就是以x为领导的答案。

最后MAX{sz[x]*L[x]}就是答案

代码如下:

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#define ull long long
#define MAXN 100010
using namespace std;
ull m,val[MAXN],sumn[MAXN],L[MAXN],ans,sz[MAXN];
int n,fa[MAXN],rig[MAXN],lef[MAXN],dist[MAXN],root[MAXN];
vector<int> g[MAXN];
int merge_lst(int x,int y)
{
    if(!x||!y) return x+y;
    if(val[x]<val[y]) swap(x,y);
    rig[x]=merge_lst(rig[x],y);
    sumn[x]=sumn[lef[x]]+sumn[rig[x]]+val[x];
    sz[x]=sz[rig[x]]+sz[lef[x]]+1;
    if(dist[lef[x]]<dist[rig[x]])
        swap(lef[x],rig[x]);
    dist[x]=dist[rig[x]]+1;
    return x;
}
inline ull getsum(int x)
{
    return sumn[x];
}
inline int getsz(int x)
{
    return sz[x];
}
void pop(int &x)
{
    x=merge_lst(rig[x],lef[x]);return;
}
int dfs(int rt)
{
    for(int i=g[rt].size()-1;i>=0;i--)
        if(g[rt][i]!=fa[rt])
        {
            dfs(g[rt][i]);
            root[rt]=merge_lst(root[rt],root[g[rt][i]]);
            while(getsum(root[rt])>m) pop(root[rt]);
        }
    ans=max(ans,L[rt]*getsz(root[rt]));
    return 0;
}
int main()
{
    scanf("%d%lld",&n,&m);
    int rt;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%lld%lld",&fa[i],&val[i],&L[i]);
        if(fa[i]==0) rt=i;sz[i]=1;
        g[fa[i]].push_back(i);
        dist[i]=lef[i]=rig[i]=0;
        root[i]=i;sumn[i]=val[i];
    }
    ans=-1;dfs(rt);
    printf("%lld\n",ans);return 0;
}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值