zoukankan      html  css  js  c++  java
  • [JSOI2016]最佳团体(分数规划+背包类树形DP)

    传送门

    个人认为本题是这道题的加强版,加强之处在于本题要用到背包类树形DP.所以不会背包类树形DP的话,可以先做这道题.也可以自行学习.

    推荐的第一道题的题解

    推荐的第二道题的题解

    题意:N个人,1-N编号,每个人价值pi,费用si,推荐人ri,如果选了i,就必须要选择他的推荐人ri,ri可以等于零(编号为0的人是特殊的,它永远在团队中,且没有价值和费用),从这N个人中选出m个人(不包括编号0的人)构成一个团队,使得团队总价值与总费用的比值最大.

    又看到了比值最大(小)问题,就想到了分数规划,理解清楚题意,列出我们要求的答案就是(frac{sum_{i=1}^{k}pi}{sum_{i=1}^{k}si})

    想到了分数规划,就想到了要二分答案,直接二分mid,如果此次二分的值成立,即(frac{sum_{i=1}^{k}pi}{sum_{i=1}^{k}si}>=mid)

    整理一下上式得(sum_{i=1}^{k} (pi-si*mid>=0))

    所以每次check时,把每个人的价值更新为(pi-si*mid),题目就转换为了有N个人,已知每个人的价值,选出k个人使得价值和>=0,有没有看到背包的影子?

    以上都是分数规划的思路的模板,现在我们来考虑这道题目的特殊之处,就是每个人都有唯一的推荐人ri,只有推荐人ri被选中,才能选择第i个人.

    因为每个人都有唯一的推荐人ri,相当于树中每个节点最多只有一个父节点,又因为有0号节点这个特殊的点存在,所以N+1个节点构成了一棵以0号节点为根节点的树,还因为题目要求最优解,于是就想到了可以树形DP.

    设f[x][i]表示在以x为根节点的子树中选择i个点能获得的最大价值(一般树形DP都是这样设的吧),不讲了.

    P.S.不知道是本题卡常太过分,还是我的代码太丑,一定要吸氧才能不超时(这道题我交了几十遍,最好的成绩都T了一个点,欢迎各位不吸氧就能过的巨佬吊打教我)

    int n,m;
    int s[2505],p[2505],r[2505],size[2505];
    double eps=1e-6;//设置二分精度
    double v[2505],f[2505][2505];
    vector<int> q[2505];
    inline void dfs(int x){
        f[x][0]=0;f[x][1]=v[x];
        for(int i=2;i<=m;i++)f[x][i]=-1e9;
    //初始化,这个很显然吧
        size[x]=1;
    //size数组表示以x为根节点的子树中的子节点的数量
    //把x视作以x为根节点的子树中的子节点,所以初始值为1
    //用于优化树形DP,不优化T飞5个点
    //不懂的话,底下推荐的第二篇博客有提到
        for(int i=0;i<q[x].size();i++){
    		int y=q[x][i];
    		dfs(y);
    		for(int j=size[x];j>=1;j--)
    	    	for(int k=0;k<=size[y];k++){
    				if(j+k>m)break;//优化
    				f[x][j+k]=max(f[x][j+k],f[x][j]+f[y][k]);
    	    	}
    		size[x]+=size[y];
        }
    }
    inline bool check(double mid){
        for(int i=1;i<=n;i++)
    		v[i]=p[i]-s[i]*mid;
    //记得根据mid更新每个人的价值
        dfs(0);//从根节点开始遍历整个树
        return f[0][m]>=0;//判定
    }
    int main(){
        m=read();n=read();
        m++;
    //因为0号节点必选,所以相当于我们要选择m+1个节点
        for(int i=1;i<=n;i++){
    		s[i]=read();
    		p[i]=read();
    		r[i]=read();
    		q[r[i]].push_back(i);
        }
    //我直接vector存图了,也可以用链式前向星
        double l=0,r=1e6,mid;
        while(l+eps<r){
    		mid=(l+r)/2.0;
    		if(check(mid))l=mid;
    		else r=mid;
        }
        printf("%.3lf
    ",l);
        return 0;
    }
    
    
    

    推荐1

    推荐2

  • 相关阅读:
    Spyder 快捷键大全
    上传代码到github,出现 git@github.com: Permission denied (publickey) 错误
    Linux ubuntu 安装 openssh-server 报错
    win10删除「此电脑」中的文档、视频、音乐、下载、图片、桌面等6个文件夹的方法
    IDEA 光标闪烁问题
    委托、Action泛型委托、Func泛型委托、Predicate泛型委托的用法
    C#6.0的新语法特性
    创建多个网站
    发送短信功能(C#)
    VS开发Windows服务
  • 原文地址:https://www.cnblogs.com/PPXppx/p/10370155.html
Copyright © 2011-2022 走看看