APIO2012 派遣

题目描述

题解:

可并堆优化$dp$。

由于$ans$只由$l$与派遣人数决定,我们可以贪心选取总和$<=m$的人。

有两种选择,一种是维护小根堆,一直$pop$到弹出的总和$>m$;

另一种是维护大根堆,一直$pop$到剩下总和$<=m$;

这两种比较一定是维护大根堆更优,因为每次$pop$后剩下的堆可以直接回溯。

然后$dp$就好了。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 100050;
template<typename T>
inline void read(T&x)
{
    T f = 1,c = 0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){c=c*10+ch-'0';ch=getchar();}
    x = f*c;
}
int n,b[N],c[N],hed[N],cnt,rt[N],dis[N],ls[N],rs[N],siz[N];
ll m,l[N],ans=0,sum[N];
struct EG
{
    int to,nxt;
}e[N];
void ae(int f,int t)
{
    e[++cnt].to = t;
    e[cnt].nxt = hed[f];
    hed[f] = cnt;
}
int merge(int x,int y)
{
    if(!x||!y)return x+y;
    if(c[x]<c[y])swap(x,y);
    rs[x] = merge(rs[x],y);
    if(dis[ls[x]]<dis[rs[x]])swap(ls[x],rs[x]);
    dis[x] = dis[rs[x]]+1;
    return x;
}
int pop(int x)
{
    return merge(ls[x],rs[x]);
}
struct Pair
{
    int x;
    ll w;
    Pair(){}
    Pair(int x,ll w):x(x),w(w){}
    friend bool operator < (Pair a,Pair b)
    {
        return a.w < b.w;
    }
};
int cal(int u)
{
    while(sum[u]>m)
    {
        sum[u]-=c[rt[u]];
        rt[u]=pop(rt[u]);
        siz[u]--;
    }
    return siz[u];
}
void dfs(int u)
{
    siz[u]=1;sum[u]=c[u];
    for(int j=hed[u];j;j=e[j].nxt)
    {
        int to = e[j].to;
        dfs(to);
        siz[u]+=siz[to];
        sum[u]+=sum[to];
        rt[u] = merge(rt[u],rt[to]);
    }
    ans = max(ans,l[u]*cal(u));
}
int main()
{
    read(n),read(m);
    for(int i=1;i<=n;i++)
    {
        read(b[i]),read(c[i]),read(l[i]);
        ae(b[i],i);
        rt[i]=i,dis[i]=1;
    }
    dfs(1);
    printf("%lld
",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/LiGuanlin1124/p/10294989.html