zoukankan      html  css  js  c++  java
  • [BZOJ4753][Jsoi2016]最佳团体

    Description

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

    Input

    输入一行包含两个正整数K和N。
    接下来N行,其中第i行包含3个整数Si,Pi,Ri表示候选人i的招募费用,战斗值和推荐人编号。
    对于100%的数据满足1≤K≤N≤2500,0<"Si,Pi"≤10^4,0≤Ri<i

    Output

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

    Sample Input

    1 2
    1000 1 0
    1 1000 1

    Sample Output

    0.001
     

     
    看到有分数那么可能是二分答案$mid$。
    如果$frac{sum P_i}{sum S_i}> mid$,即$sum P_i-mid imes sum S_i > 0$,那么l就应该增大。
    这样我们可以把每个点的权值看成$P_i - S_i imes mid$, 然后做树形背包看最后的最大值是否大于0就行了。
    树形背包细节太多了...调了一节课...
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    #define reg register
    inline int read() {
        int res = 0;char ch=getchar();bool fu=0;
        while(!isdigit(ch))fu|=(ch=='-'),ch=getchar();
        while(isdigit(ch)) res=(res<<3)+(res<<1)+(ch^48),ch=getchar();
        return fu?-res:res;
    }
    #define N 2505
    int K, n;
    int P[N], S[N];
    
    struct edge {
        int nxt, to;
    }ed[N];
    int head[N], cnt;
    inline void add(int x, int y) {
        ed[++cnt] = (edge){head[x], y};
        head[x] = cnt;
    }
    
    double f[N][N], val[N];
    int siz[N], dfn[N], tot;
    
    void dfs(int x)
    {
        siz[x] = 1;
        for (reg int i = head[x] ; i ; i = ed[i].nxt)
        {
            int to = ed[i].to;
            dfs(to);
            siz[x] += siz[to];
        }
        dfn[x] = ++tot;
    }
    
    void dp(int x)
    {
        siz[x] = 1;
        for (reg int i = head[x] ; i ; i = ed[i].nxt)
        {
            int to = ed[i].to;
            dp(to);
            for (reg int j = siz[x] ; j >= 1 ; j --)
                for (reg int p = 0 ; p <= siz[to] ; p ++)
                    f[x][j + p] = max(f[x][j + p], f[to][p] + f[x][j]);
            siz[x] += siz[to];
        }
    }
    
    int main()
    {
        K = read() + 1, n = read();
        for (reg int i = 1 ; i <= n ; i ++) S[i] = read(), P[i] = read(), add(read(), i);
        dfs(0);
        double l = 0, r = 1e4, mid(0);
        while(r - l >= 1e-5)
        {
            mid = (l + r) / 2;
            for (reg int i = 1 ; i <= n ; i ++) val[i] = 1.0 * P[i] - 1.0 * S[i] * mid;
               for (reg int i = 0 ; i <= n ; i ++)
                   for (reg int j = 0 ; j <= K ; j ++)
                       f[i][j] = -1e9;
            for (reg int i = 0 ; i <= n ; i ++) f[i][0] = 0;
            for (reg int i = 0 ; i <= n ; i ++) f[i][1] = val[i];
            dp(0);
            if (f[0][K] >= 1e-5) l = mid;
            else r = mid;
        }
        printf("%.3lf
    ", l);
        return 0;
    }
    树形背包

    对于这种有依赖的树形背包,有一种比较常见的优化方法,这种方法只是针对这种选择一个点必须选择其父亲的树上背包问题。

    借鉴自一个巨佬的博客

    我们求出这棵树的后序遍历,对于一个点的dfs序是$i$,那么它的子树一定是$[i - siz[x], i]$的一段连续区间,枚举这个点选不选,选的话从$f[i-1]$转移过来,不选的话从$f[i-siz[i]]$转移过来,复杂度$O(NM)$


    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    #define reg register
    inline int read() {
        int res = 0;char ch=getchar();bool fu=0;
        while(!isdigit(ch))fu|=(ch=='-'),ch=getchar();
        while(isdigit(ch)) res=(res<<3)+(res<<1)+(ch^48),ch=getchar();
        return fu?-res:res;
    }
    #define N 2505
    int K, n;
    int P[N], S[N];
    
    struct edge {
        int nxt, to;
    }ed[N];
    int head[N], cnt;
    inline void add(int x, int y) {
        ed[++cnt] = (edge){head[x], y};
        head[x] = cnt;
    }
    
    double f[N][N], val[N];
    int siz[N], dfn[N], tot;
    
    void dfs(int x)
    {
        siz[x] = 1;
        for (reg int i = head[x] ; i ; i = ed[i].nxt)
        {
            int to = ed[i].to;
            dfs(to);
            siz[x] += siz[to];
        }
        dfn[x] = ++tot;
    }
    
    void dp(int x)
    {
        siz[x] = 1;
        for (reg int i = head[x] ; i ; i = ed[i].nxt)
        {
            int to = ed[i].to;
            dp(to);
            siz[x] += siz[to];
        }
        for (reg int j = 1 ; j <= K ; j ++)
            f[dfn[x]][j] = max(f[dfn[x] - siz[x]][j], f[dfn[x] - 1][j - 1] + val[x]);    
    }
    
    int main()
    {
        K = read() + 1, n = read();
        for (reg int i = 1 ; i <= n ; i ++) S[i] = read(), P[i] = read(), add(read(), i);
        dfs(0);
        double l = 0, r = 1e7, mid(0);
        while(r - l >= 1e-6)
        {
            mid = (l + r) / 2;
            for (reg int i = 0 ; i <= n ; i ++) 
            {
                f[i][0] = 0;
                for (reg int j = 1 ; j <= K ; j ++)
                    f[i][j] = -1e9;
            }    
            for (reg int i = 1 ; i <= n ; i ++) val[i] = 1.0 * P[i] - 1.0 * S[i] * mid;
            dp(0);
            if (f[tot][K] >= 1e-6) l = mid;
            else r = mid;
        }
        printf("%.3lf
    ", l);
        return 0;
    }
  • 相关阅读:
    luogu1803 凌乱的yyy / 线段覆盖
    luogu1051 谁拿了最多奖学金
    luogu1208 [USACO1.3]混合牛奶 Mixing Milk
    luogu1090合并果子
    Lab 1 : Part 1 exercise 2
    动态规划(DP)笔记(三):常见普通题型
    leetcode 213. 打家劫舍II: 动态规划(c++)
    动态规划(DP)笔记(二): 序列型及简单例题
    Lab1: Booting a PC
    动态规划(DP)笔记(一): 简介
  • 原文地址:https://www.cnblogs.com/BriMon/p/9832595.html
Copyright © 2011-2022 走看看