P4322 [JSOI2016] 最佳团体

发布时间 2023-08-01 08:41:39作者: trh0630

一、题目描述:

  给你一颗 $n$ 个节点的有根树。节点 $i$ 的价值为 $v_i$,费用为 $w_i$。

  你需要选择 $k$ 个节点,使得 $\frac{\sum_{i=1}^nv_i}{\sum_{i=1}^nw_i}$ 最大。

  约束:选择一个节点之前,必须先选择它的父亲节点。(根节点除外)

  输出 $\frac{\sum_{i=1}^nv_i}{\sum_{i=1}^nw_i}$ 的最大值。答案精确到三位小数。

  数据范围 $1\le k\le n\le 2500,1\le v_i,w_i \le 1\times 10^4$。


 二、解题思路:

  是我以前从未见过的分数规划题目。

  分数规划题的形式就是让你求 $\frac{\sum_{i=1}^kv_i}{\sum_{i=1}^kw_i}$ 的最值。

  其实写了一道题就不难了,个人觉得比较套路。一般我们二分求解分数规划。过程如下:

  $二分枚举答案\ mid\ $

  $若\ mid\ 成立,说明存在一组树上的点使得\ \frac{\sum_{i=1}^kv_i}{\sum_{i=1}^kw_i}>=mid\ $

  $化一下式子,得到\ \sum_{i=1}^k(v_i-mid\times w_i)>=0\ $

  $令每个点\ x\ 的权值\ g(x)=v_x-mid\times w_x\ $

  $问题转化为求\ k\ 个最大的\ g(x),是否和大于0\ $

  于是就是比较板的树形背包题了,时间复杂度 $O(nk\times log_2^{1e8})$


 三、完整代码:

 1 #include<iostream>
 2 #include<iomanip>
 3 #include<algorithm>
 4 #define N 3010
 5 #define ll long long
 6 #define rep(i,l,r) for(int i=l;i<=r;i++)
 7 #define per(i,r,l) for(int i=r;i>=l;i--)
 8 #define tep(i,u) for(int i=head[u];i!=-1;i=e[i].nxt)
 9 using namespace std;
10 int n,m,s[N];
11 double g[N],v[N],w[N],f[N][N];
12 struct EDGE{
13     int v,nxt;
14 }e[N*2];
15 int head[N],cnt;
16 void add(int u,int v){
17     e[++cnt].v=v;
18     e[cnt].nxt=head[u];
19     head[u]=cnt;
20 }
21 void Max(double &a,double b){a=max(a,b);}
22 void solve(int u,int ff){
23     tep(i,u){
24         if(e[i].v==ff) continue;
25         int to=e[i].v;solve(to,u);
26         per(j,min(s[u]+s[to],m),2)
27             rep(k,max(1,j-s[u]),min(s[to],j-1))
28                 Max(f[u][j],f[u][j-k]+f[to][k]);
29         s[u]+=s[to];
30     }
31 }
32 bool check(double val){
33     rep(i,1,n)
34         g[i]=v[i]-val*w[i];
35     rep(i,0,n)
36         rep(j,0,m)
37             f[i][j]=-1e9;
38     rep(i,0,n)
39         s[i]=1,f[i][1]=g[i];
40     solve(0,0);
41     return f[0][m]>=0;
42 }
43 int main(){
44     ios::sync_with_stdio(false);
45     cin.tie(0);cout.tie(0);
46     cin>>m>>n;m++;
47     rep(i,0,n)
48         head[i]=-1;
49     rep(i,1,n){
50         cin>>w[i]>>v[i];
51         int ff;cin>>ff,add(ff,i);
52     }
53     double l=0,r=1e4,eps=0.0003;
54     while(l+eps<=r){
55         double mid=(l+r)/2;
56         if(check(mid)) l=mid+eps;
57         else r=mid-eps;
58     }
59     cout<<fixed<<setprecision(3)<<l<<'\n';
60     return 0;
61 }

四、写题心得:

  收获经验如下:

  $1、新知识 \to 分数规划。=> Exp++!$

  $2、树形背包竟然是\ O(n^2)\ 的,震惊 ! => Exp++!$