bzoj4753 最佳团体

题目描述

JSOI 信息学代表队一共有 NN 名候选人,这些候选人从 11 到 NN 编号。方便起见,JYY 的编号是 00 号。每个候选人都由一位编号比他小的候选人R_iRi 推荐。如果 R_i = 0Ri=0​,则说明这个候选人是 JYY 自己看上的。

为了保证团队的和谐,JYY 需要保证,如果招募了候选人 ii,那么候选人 R_iRi 也一定需要在团队中。当然了,JYY 自己总是在团队里的。每一个候选人都有一个战斗值 P_iPi ,也有一个招募费用 S_iSi 。JYY 希望招募 KK 个候选人(JYY 自己不算),组成一个性价比最高的团队。也就是,这 KK 个被 JYY 选择的候选人的总战斗值与总招募费用的比值最大。

输入输出格式

输入格式:

 

输入一行包含两个正整数 KK 和 NN 。

接下来 NN 行,其中第 ii 行包含三个整数 S_iSi , P_iPi , R_iRi , 表示候选人 ii 的招募费用,战斗值和推荐人编号。

 

输出格式:

 

输出一行一个实数,表示最佳比值。答案保留三位小数。

 

这题涉及到了比值最大,最好用分数规划来解决。

我们需要求出pi和ri的比值最大,不妨设Σpi/Σri>=x  ,经过转移Σpi>=Σri*x => Σpi-Σri*x>=0. 由此可见,我们可以二分出来一个x使这个值>=0.

然后我们可以用树形DP来计算出最优值。

我们先DFS一遍得到每个树上节点的DFS序(时间戳),令f[i][j]为DFS序为i的点,取j个的最优值。

如果当前点取,说明自己的子树也可以取,所以f[i+1][j+1]=max(f[i+1][j+1],f[i][j]+val[i]);

如果当前点不取,说明要取到下一颗树,我们记录size[i]代表以i为根的子树的大小。根据DFS序的性质,我们知道下一颗和自己平行的子树的DFS序为i+size.

所以转移方程是:f[i+size[i]][j]=max(f[i+size[i]][j],f[i][j]);

 

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <stack>
#define in(a) a=read()
#define MAXN 200020
#define REP(i,k,n) for(int i=k;i<=n;i++)
using namespace std;
inline int read(){
    int x=0,f=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar())
        if(ch=='-')
            f=-1;
    for(;isdigit(ch);ch=getchar())
        x=x*10+ch-'0';
    return x*f;
}
int k,n;
int INF=99999999;
double p[2510],s[2510];
int total=0,to[2520],nxt[2510],head[2510];
int cnt=0,dfn[2510],ind[2510],size[2510];
double f[2510][2510],val[2510];
inline void adl(int a,int b){
    total++;
    to[total]=b;
    nxt[total]=head[a];
    head[a]=total;
    return ;
}
inline void DFS(int u){
    dfn[u]=cnt;
    ind[cnt++]=u;
    size[u]=1;
    for(int e=head[u];e;e=nxt[e]){
        DFS(to[e]);
        size[u]+=size[to[e]];
    }
    return ;
}
inline double DP(double x){
    //cout<<x<<endl;
    REP(i,1,n){
        val[i]=p[ind[i]]-x*s[ind[i]];
        //cout<<ind[i]<<" "<<val[i]<<endl;
    }
    REP(i,1,n+1)
        REP(j,0,k+1)
            f[i][j]=-INF;
    REP(i,0,n)
        REP(j,0,min(i,k+1)){
            f[i+1][j+1]=max(f[i+1][j+1],f[i][j]+val[i]);
            f[i+size[ind[i]]][j]=max(f[i+size[ind[i]]][j],f[i][j]);
        }
    /*REP(i,0,n){
        REP(j,0,min(i,k+1))
            cout<<f[i][j]<<" ";
        cout<<endl;
    }*/
    return f[n+1][k+1];
}
int main(){
    in(k);in(n);
    int a;
    double maxn=-INF;
    REP(i,1,n){
        scanf("%lf%lf%d",&s[i],&p[i],&a);
        adl(a,i);
        maxn=max(maxn,p[i]);
    }
    DFS(0);
    val[0]=0.0;
    double left=0.0,right=maxn;
    while(right-left>0.00001){
        double mid=(left+right)/2.0;
        //cout<<left<<" "<<right<<" "<<mid<<endl;
        if(DP(mid)>=0.00001)  left=mid;
        else  right=mid;
    }
    printf("%.3lf",left);
    return 0;
}
/*
2 4
1 2 0
2 2 0
1 3 1
2 3 1
*/

 

 

 

转载于:https://www.cnblogs.com/jason2003/p/9737461.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值