[JZOJ4512][JSOI2016]最佳团队

题目大意

一棵树,有 n+1 个节点,根编号为 0
每个非根节点都有两个权值si pi ,父亲 ri
要求选择 K+1 个节点,最大化

pisi

并且所选节点一定包括根,并且如果选择了节点 x(x0) 那么 x 的父亲ri一定要选。

1Kn2500,0<si,pi104,0ri<i


题目分析

看见最大化的分式,显然要使用 01 分数规划。
二分答案转化为判定性问题,计算是否存在权值和小于零的选择方案。
由于这里有树形依赖关系,那我们需要用一个神奇的方法做树形 dp
注意到 x 的子树中任一节点能选择当且仅当x被选择。
所以我们可以按照 DFS 序列来 dp ,令 fi,j 表示待处理节点在 DFS 序中排名为 i ,已经选择了j个节点,令其节点编号为 p
如果选择了 p ,那么其子树就可以考虑,转移到fi+1,j+1
如果不选择 p ,那么就要跳过其子树,转移到fi+sizep,j
这个 dp 很巧妙,利用了树形依赖的性质。
时间复杂度 O(nKlog2(ns))


代码实现

#include <iostream>
#include <climits>
#include <cfloat>
#include <cstdio>
#include <cctype>

using namespace std;

typedef double db;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=(ch=='-')?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const db INF=DBL_MAX/3;
const db EPS=0.0001;
const int N=2550;
const int K=2550;

int fa[N],last[N],tov[N],next[N],s[N],p[N],size[N],id[N];
int n,k,tot,idx,sum;
db f[N][K],v[N];
db ans;

void insert(int x,int y)
{
    tov[++tot]=y,next[tot]=last[x],last[x]=tot;
}

void dfs(int x)
{
    size[id[++idx]=x]=1;
    for (int i=last[x],y;i;i=next[i])
        dfs(y=tov[i]),size[x]+=size[y];
}

bool check(db now)
{
    for (int i=2;i<=n+1;i++)
    {
        v[i]=s[id[i]]*now-p[id[i]];
        for (int j=0;j<=k;j++)
            f[i][j]=INF;
    }
    f[2][0]=0;
    for (int i=2;i<=n;i++)
    {
        for (int j=0;j<=k&&j<=i-2;j++)
        {
            if (f[i][j]<f[i+size[id[i]]][j]) f[i+size[id[i]]][j]=f[i][j];
            if (j<k&&f[i+1][j+1]>f[i][j]+v[i]) f[i+1][j+1]=f[i][j]+v[i];
        }
    }
    return f[n+1][k]>0;
}

double binary_search()
{
    db l=0,r=sum,mid,ret;
    while (l+EPS<r)
    {
        mid=(l+r)/2;
        if (check(mid)) r=mid;
        else ret=mid,l=mid;
    }
    return ret;
}

int main()
{
    freopen("team.in","r",stdin),freopen("team.out","w",stdout);
    k=read(),n=read();
    for (int i=1;i<=n;i++)
    {
        s[i+1]=read(),sum+=(p[i+1]=read()),fa[i+1]=read();
        insert(++fa[i+1],i+1);
    }
    n++;
    dfs(1);
    ans=binary_search();
    printf("%.3lf\n",ans);
    fclose(stdin),fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
根据引用[1],dp[u][j]表示在u子树中选取恰好j个人时能获得的最大价值。而根据引用,该问题的时间复杂度为O(log2​104×nm)。 对于洛谷P2143 [JSOI2010] 巨额奖金问题,我们可以使用动态规划来解决。具体步骤如下: 1. 首先,我们需要构建一棵树来表示员工之间的关系。树的根节点表示公司的总经理,其他节点表示员工。每个节点都有一个权值,表示该员工的奖金金额。 2. 接下来,我们可以使用动态规划来计算每个节点的dp值。对于每个节点u,我们可以考虑两种情况: - 如果选择节点u,则dp[u][j] = dp[v][j-1] + value[u],其中v是u的子节点,value[u]表示节点u的奖金金额。 - 如果不选择节点u,则dp[u][j] = max(dp[v][j]),其中v是u的子节点。 3. 最后,我们可以通过遍历树的所有节点,计算出dp[u][j]的最大值,即为所求的巨额奖金。 下面是一个示例代码,演示了如何使用动态规划来解决洛谷P2143 [JSOI2010] 巨额奖金问题: ```python # 构建树的数据结构 class Node: def __init__(self, value): self.value = value self.children = [] # 动态规划求解最大奖金 def max_bonus(root, j): dp = [[0] * (j+1) for _ in range(len(root)+1)] def dfs(node): if not node: return for child in node.children: dfs(child) for k in range(j, 0, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-1] + node.value) for child in node.children: for k in range(j, 0, -1): for l in range(k-1, -1, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-l-1] + dp[child.value][l]) dfs(root) return dp[root.value][j] # 构建树 root = Node(1) root.children.append(Node(2)) root.children.append(Node(3)) root.children[0].children.append(Node(4)) root.children[0].children.append(Node(5)) root.children[1].children.append(Node(6)) # 求解最大奖金 j = 3 max_bonus_value = max_bonus(root, j) print("最大奖金为:", max_bonus_value) ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值