• 【bzoj4753】[Jsoi2016]最佳团体 分数规划+树形背包dp


    题目描述

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

    输入

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

    输出

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

    样例输入

    1 2
    1000 1 0
    1 1000 1

    样例输出

    0.001


    题解

    分数规划+树形背包dp

    二分答案mid,题目便转化为求是否存在满足题目条件的集合V,使得$frac{sumlimits_{iin V}p_i}{sumlimits_{iin V}s_i}ge mid$,即$sumlimits_{iin V}(s_i-mid·p_i)ge 0$。

    这就转化为了一个树形dp问题。

    令a[i]=s[i]-mid*p[i],表示i的性价比。设f[i][j]表示从子树i中选出j个且选i,可以获得的最大性价比之和,显然f[i][1]=a[i]。

    那么对于每个i的子节点son,相当于有体积为1~si[son]共si[son]个物品放入背包内,每个物品可以放或不放。这相当于01背包问题。

    但是这样dp的时间复杂度好像是$O(n^3)$的。

    事实上,这里面的有效状态是很少的,如果只枚举有效状态,dp的时间复杂度将到达可以接受的$O(n^2)$。

    具体粗略证明:

    更新一棵子树的时间复杂度=更新该节点的子节点的时间复杂度+计算该节点的时间复杂度。

    计算该节点的复杂度,如果采用最优策略,使用严格的有效区间范围来进行dp,时间复杂度应该为

    $O(sumlimits_{i=1}^m(1+sumlimits_{j=1}^{i-1}si_j)·si_i)=O(sumlimits_{i=1}^msumlimits_{j=1}^{i-1}si_j·si_i+sumlimits_{i=1}^msi_i)=O((sumlimits_{i=1}^msi_i)^2-sumlimits_{i=1}^msi_i^2+sumlimits_{i=1}^msi_i)=O(si_x^2-sumlimits_{i=1}^msi_i^2+si_x)$,

    其中$si_i(iin[1,m])$表示x的第i个儿子节点的子树大小(总共有m个儿子节点),$si_x$表示x的子树大小。

    而叶子节点的时间复杂度是$O(1)$的,进而我们可以使用累加法计算出总体dp的时间复杂度为$O(si_{root}^2+sumlimits_{i=1}^nsi_i^2)=O(n^2)$。

    因此总的时间复杂度是$O(n^2log m)$。

    为了避免精度误差带来的答案错误,建议固定二分c次,c值视情况而定,本题中取30可过。

    另外由于数据太水了,所以$O(n^3log m)$的做法也是可以通过本题的。(其实可以自己做一个链的数据卡掉它)

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 2510
    using namespace std;
    int head[N] , to[N] , next[N] , cnt , si[N] , w[N] , v[N] , n;
    double a[N] , mid , f[N][N];
    void add(int x , int y)
    {
    	to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
    }
    void init(int x)
    {
    	int i;
    	si[x] = 1;
    	for(i = head[x] ; i ; i = next[i]) init(to[i]) , si[x] += si[to[i]];
    }
    void dfs(int x)
    {
    	int i , j , k , tot = 0 , b = 0;
    	memset(f[x] , 0xc2 , sizeof(f[x]));
    	if(x) f[x][1] = a[x] , tot ++ , b ++ ;
    	else f[x][0] = 0;
    	for(i = head[x] ; i ; i = next[i])
    	{
    		dfs(to[i]);
    		for(j = tot ; j >= b ; j -- )
    			for(k = 1 ; k <= si[to[i]] ; k ++ )
    				f[x][j + k] = max(f[x][j + k] , f[x][j] + f[to[i]][k]);
    		tot += si[to[i]];
    	}
    }
    int main()
    {
    	int n , k , i , x , c = 30;
    	double l = 0 , r = 0;
    	scanf("%d%d" , &k , &n);
    	for(i = 1 ; i <= n ; i ++ ) scanf("%d%d%d" , &w[i] , &v[i] , &x) , add(x , i) , r = max(r , (double)v[i]);
    	init(0);
    	while(c -- )
    	{
    		mid = (l + r) / 2;
    		for(i = 1 ; i <= n ; i ++ ) a[i] = v[i] - mid * w[i];
    		dfs(0);
    		if(f[0][k] >= 0) l = mid;
    		else r = mid;
    	}
    	printf("%.3lf
    " , (l + r) / 2);
    	return 0;
    }
    

     

  • 相关阅读:
    生成器和推导式
    闭包
    python
    python初识函数二
    python函数初识
    python文件操作二
    文件操作
    python集合,深浅copy
    Python安装、配置图文详解
    jsDoc 使用及配置!
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/7040678.html
Copyright © 2020-2023  润新知