洛谷1552 [APIO2012]派遣(贪心)(左偏树)

16 篇文章 0 订阅
10 篇文章 0 订阅

题意

选择一个节点x,并在其子树中选num个节点,它们的薪水不能超过m,满意度为x.l*num,求最大满意度。

特性

在一棵子树中,选的节点越多越好,所以要选薪水尽量小的节点。

尝试

这题很满足可并堆,自底向上,建许多的小根堆,每个节点先把所有子节点的小根堆给并起来,那么每次的决策就是一直取堆顶直到不能再取,记下num就好了。
但是这么做时间复杂度太大了。

题解

贪心+可并堆
小的不行就用大的嘛!
建立大根堆,当堆中薪水和大于m时,首选踢出堆顶,直到小于等于m。同时维护堆中元素个数,这样就可以很轻松的接替小根堆的工作了。
在合并时,也不必考虑那么多,子节点的小根堆都是尽量大的了,也是在子节点中最优的了。把它们合并起来时,原先已被踢出的点显然没有价值,这样每个点只会被踢出一次,大大降低了时间复杂度。

代码

这题不需要记录fa了,因为它是一棵树,合并流程十分清晰,可以直接得到fa。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=100010;

int n,m;
int l[MAXN];

struct E{int y,next;}e[MAXN];int len=0,last[MAXN];
void ins(int x,int y)
{
    e[++len]=(E){y,last[x]};last[x]=len;
}

int rt[MAXN],ch[MAXN][2];
ll sum[MAXN];
int a[MAXN],dis[MAXN],num[MAXN];
int merge(int x,int y)
{
    if(!x || !y) return x+y;
    if(a[x]<a[y]) swap(x,y);
    ch[x][1]=merge(ch[x][1],y);
    if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][0],ch[x][1]);
    dis[x]=dis[ch[x][1]]+1;
    return x;
}
int pop(int x)
{
    return merge(ch[x][0],ch[x][1]);
}

int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        rt[i]=i;num[i]=1;=
        int x,y=i;
        scanf("%d %d %d",&x,&a[i],&l[i]);sum[i]=a[i];
        ins(x,y);
    }
    
    ll ans=0;
    for(int x=n;x>=1;x--)
    {
        for(int k=last[x];k;k=e[k].next)
        {
            int y=e[k].y;
            rt[x]=merge(rt[x],rt[y]);
            sum[x]+=sum[y];num[x]+=num[y];
        }
        while(num[x] && sum[x]>m)
        {
            sum[x]-=a[rt[x]];num[x]--;
            rt[x]=pop(rt[x]);//在pop后要记录新的rt
        }
        ans=max(ans,(ll)num[x]*l[x]);
    }
    printf("%lld\n",ans);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值