APIO 2012 派遣(可并堆)

APIO 2012 派遣(可并堆)

给定一棵N个点的树和M,每个点有两个权值ai,bi,每次可以选择一个点x,然后在这个点的子树中选若干点(可以不选自己),使得这些点的\(\sum b_i<=M\)。收益为ax*选出的点个数。求最大收益。

对每个点维护一个大根堆即可~~

#include <cstdio>
using namespace std;

typedef long long LL;
const LL maxn=2e5+5;
void swap(LL &x, LL &y){ LL t=x; x=y; y=t; }
LL max(LL x, LL y){ return x<y?y:x; }

struct Edge{
    LL to, nxt;
}e[maxn];
LL cnte, fir[maxn], ans;
void addedge(LL x, LL y){
    Edge &ed=e[++cnte];
    ed.to=y; ed.nxt=fir[x]; fir[x]=cnte; }

LL n, M, rt, b[maxn], v[maxn], sum[maxn], cnt[maxn];
//要维护关于薪水b的大根堆 sum表示堆内所有b的和 cnt表示堆内有多少点

struct LHeap{
    LL l, r, dis, fa;
}h[maxn];

LL find(LL x){ return h[x].fa==x?x:find(h[x].fa); }

LL merge(LL x, LL y){  //把x和y为根的树合并
    if (!x||!y) return x+y;
    if (b[x]<b[y]) swap(x, y);
    LL &lx=h[x].l, &rx=h[x].r;
    rx=merge(rx, y); h[rx].fa=x;  //把右子树和y树合并
    if (h[lx].dis<h[rx].dis) swap(lx, rx);
    h[x].dis=h[rx].dis+1;
    sum[x]=sum[lx]+sum[rx]+b[x];
    cnt[x]=cnt[lx]+cnt[rx]+1;
    return x;  //返回根的编号
}

LL del(LL x){  //删除树x的根结点,返回新根的编号
    LL lx=h[x].l, rx=h[x].r;
    h[lx].fa=lx; h[rx].fa=rx;
    return merge(lx, rx);
}

LL dfs(LL x){  //返回合并后大根堆的根结点编号 刚开始的时候,x的堆根结点的编号就是x
    LL u=x;
    for (LL i=fir[x]; i; i=e[i].nxt)
        x=merge(dfs(e[i].to), x);  //不停合并子树的堆
    while (sum[x]>M) x=del(x);  //使得当前的堆的sumb<=M
    ans=max(ans, v[u]*cnt[x]);
    return x;
}

int main(){
    scanf("%lld%lld", &n, &M); LL t;
    for (LL i=1; i<=n; ++i){
        scanf("%lld%lld%lld", &t, &b[i], &v[i]);
        if (t) addedge(t, i); else rt=i;
        h[i].fa=i; sum[i]=b[i]; cnt[i]=1;
    }
    dfs(rt); printf("%lld\n", ans);
    return 0;
}

转载于:https://www.cnblogs.com/MyNameIsPc/p/9090560.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值