「JSOI 2016」最佳团体

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4753


Description

  有 N N 名候选人,从 1 N N 编号,有一个队长的编号为 0,每个候选人都由一位编号比他小的候选人推荐(如果为 0 0 则表示是队长推荐的)。队长希望招募 K 个人,但是他需要保证:如果招募了 x x ,那么他的推荐人也要招募(队长总是在团队里的)。每个人都有代价 Ci 和价值 Wi W i 两个值,招募可以获得的价值为 Answer=WiCi A n s w e r = ∑ W i ∑ C i ,你需要最大化这个值,答案保留 3 3 位小数。

  1KN2500 1Ci,Wi104 1 ⩽ C i , W i ⩽ 10 4


Solution

  先做一个小转化:以 0 0 为根节点,选择 K+1 个点,其中根节点必须选择。
  首先我们考虑树形 DP DP ,显然是一道普通的树上背包问题。但是由于答案是一个比值,无法记录状态进行转移,所以我们引入 01 01 分数规划
  这个式子显然是可以二分答案的,因此可以得到

WiCix ∑ W i ∑ C i ⩾ x

  由于分母是正数,我们将式子拆开

Wix×CiWix×Ci ∑ W i ⩾ x × ∑ C i ⟹ ∑ W i ⩾ ∑ x × C i

  可得

Wix×Ci0 ∑ W i − x × C i ⩾ 0

  故我们可以二分答案 x x ,将问题转化为:每个点有 1 个属性 Pi=Wix×Ci P i = W i − x × C i ,能否使选取的 K K 个点的属性和大于等于 0

  设计状态: f[i][j] f [ i ] [ j ] 表示在以第 i i 个节点为根的子树中,选择 j 个节点的最大属性和。
   DP DP 为朴素的树上背包问题,转移非常显然(注意根节点 i i 对应的 j 至少为 1 1 ,我就因为这个调试了半天)。
  注意初始化:f[i][0]=0 f[i][1]=Wix×Ci f [ i ] [ 1 ] = W i − x × C i ,其余状态的值均为无穷小。

  时间复杂度 Θ(N2) Θ ( N 2 ) (复杂度证明见 「HAOI 2015」树上染色


Code

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=2505;
const double eps=1e-5;
int n,m,tot,c[N],s[N],hd[N],sz[N];
double w[N],f[N][N];
struct Edge{
    int to,nxt;
}e[N];

void add(int u,int v) {
    e[++tot].to=v;
    e[tot].nxt=hd[u];
    hd[u]=tot;
}
void dfs(int x) {
    sz[x]=1; f[x][1]=w[x];
    for(int i=hd[x];i;i=e[i].nxt) {
        int v=e[i].to; dfs(v);
        for(int j=sz[x];j>=1;--j)
            for(int k=0;k<=sz[v];++k)
                f[x][j+k]=max(f[x][j+k],f[x][j]+f[v][k]);
        sz[x]+=sz[v];
    }
}
bool check(double x) {
    memset(f,-10,sizeof(f));
    for(int i=0;i<=n;++i) f[i][0]=0;
    for(int i=1;i<=n;++i) w[i]=(double)s[i]-x*c[i];
    dfs(0);
    return f[0][m]>=0;
}
int main() {
    scanf("%d%d",&m,&n); ++m;
    for(int fa,i=1;i<=n;++i) {
        scanf("%d%d%d",&c[i],&s[i],&fa);
        add(fa,i);
    }
    double l=0,r=1e4,ans=0;
    while(l+eps<=r) {
        double mid=(l+r)/2;
        if(check(mid)) ans=mid,l=mid+eps; else r=mid-eps;
    }
    printf("%.3lf\n",ans);
    return 0;
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值