[BZOJ]4753: [Jsoi2016]最佳团体 01分数规划+树形DP

29 篇文章 0 订阅
7 篇文章 0 订阅

Description

JSOI信息学代表队一共有N名候选人,这些候选人从1到N编号。方便起见,JYY的编号是0号。每个候选人都由一位编号比他小的候选人Ri推荐。如果Ri=0则说明这个候选人是JYY自己看上的。为了保证团队的和谐,JYY需要保证,如果招募了候选人i,那么候选人Ri”也一定需要在团队中。当然了,JYY自己总是在团队里的。每一个候选人都有一个战斗值Pi”,也有一个招募费用Si”。JYY希望招募K个候选人(JYY自己不算),组成一个性价比最高的团队。
也就是,这K个被JYY选择的候选人的总战斗值与总招募总费用的比值最大。

题解:

今天学了下01分数规划,大概是解决这样一个问题:有一些东西,只能选或者不选,每个东西有a[i],b[i]两个值,使得最后Sigma(a[i])/Sigma(b[i])最大(或最小)。既然要最大,那么Sigma(a[i])/Sigma(b[i])>=x,就要求这个x最大,我们可以二分这个x的值,然后转为判定性问题:是否存在Sigma(a[i]-x×b[i])>=0,对于这道题,我们可以做树形背包DP,每个点的价值就重新赋值为p[i]-mid×s[i],f[i][j]表示i这个点一定选,总共选了j个点的最大价值,最后看一下f[0][k]是否大于等于0就好了。树形DP的时候要注意,一定要尽量减少无效状态,具体地来说,就是从一个有效状态转移到另一个有效状态,否则会TLE。网上有对时间复杂度的证明,是 O(n2) 的,但蒟蒻并不会证。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int Maxn=2510;
int size[Maxn],k,n,R[Maxn];
double s[Maxn],p[Maxn],f[Maxn][Maxn],v[Maxn];
struct Edge{int y,next;}e[Maxn];
int last[Maxn],len=0;
void ins(int x,int y)
{
    int t=++len;
    e[t].y=y;e[t].next=last[x];last[x]=t;
}
void get(int x)
{
    size[x]=1;
    for(int i=last[x];i;i=e[i].next)
    get(e[i].y),size[x]+=size[e[i].y];
}
void Clear(int x)
{
    for(int i=0;i<=n;i++)
    f[x][i]=-2147483647.0;
}
void dfs(int x)
{
    Clear(x);
    int tot=0;
    if(x)tot++,f[x][1]=v[x];
    else f[x][0]=0.0;
    for(int i=last[x];i;i=e[i].next)
    {
        int y=e[i].y,d=(x)?1:0;
        dfs(y);
        for(int j=tot;j>=d;j--)
        for(int l=1;l<=size[y];l++)
        f[x][j+l]=max(f[x][j+l],f[x][j]+f[y][l]);
        tot+=size[y];
    }
}
int main()
{
    double l=0.0,r=0.0;
    scanf("%d%d",&k,&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lf%lf%d",&s[i],&p[i],&R[i]);
        ins(R[i],i);r=max(r,p[i]);
    }
    get(0);
    int c=30;
    while(c--)
    {
        double mid=(l+r)/2.0;
        for(int i=1;i<=n;i++)v[i]=p[i]-mid*s[i];
        dfs(0);
        if(f[0][k]>=0.0)l=mid;
        else r=mid;
    }
    printf("%.3lf",(l+r)/2.0);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值