题目大意:
给定一棵树,
n
n
n个节点。每个节点有
a
i
a_i
ai和
b
i
b_i
bi两个值。要求选出若干节点,保证某个节点被选,他的父亲一定被选。
使
∑
i
=
1
k
a
i
∑
i
=
1
k
b
i
\frac{\sum_{i=1}^{k}a_i}{\sum_{i=1}^{k}b_i}
∑i=1kbi∑i=1kai最大化。
n ≤ 1000 n≤1000 n≤1000
分析:
考虑分数规划,二分一个
m
i
d
mid
mid,
当
m
i
d
mid
mid合法时,有
∑
i
=
1
k
a
i
∑
i
=
1
k
b
i
≥
m
i
d
\frac{\sum_{i=1}^{k}a_i}{\sum_{i=1}^{k}b_i}≥mid
∑i=1kbi∑i=1kai≥mid
即
∑
i
=
1
k
a
i
−
m
i
d
∗
∑
i
=
1
k
b
i
≥
0
\sum_{i=1}^{k}a_i-mid*\sum_{i=1}^{k}b_i≥0
i=1∑kai−mid∗i=1∑kbi≥0
也就是
∑
i
=
1
k
a
i
−
m
i
d
∗
b
i
≥
0
\sum_{i=1}^{k}a_i-mid*b_i≥0
i=1∑kai−mid∗bi≥0
把点的权值设为
a
i
−
m
i
d
∗
b
i
a_i-mid*b_i
ai−mid∗bi,考虑树上dp。设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示
i
i
i的子树中选
j
j
j个的最大值,看是否≥0。
转移要使用枚举
s
i
z
e
size
size 的方法,dp复杂度是
O
(
n
2
)
O(n^2)
O(n2)的。
代码:
// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <cmath>
const int maxn=2507;
const double esp=1e-5;
using namespace std;
int n,m,cnt,x;
double l,r,mid,ans;
int a[maxn],b[maxn],size[maxn],ls[maxn];
double f[maxn][maxn],tmp[maxn],val[maxn];
struct edge{
int y,next;
}g[maxn*2];
void add(int x,int y)
{
g[++cnt]=(edge){y,ls[x]};
ls[x]=cnt;
}
void dfs(int x,int fa)
{
size[x]=1,f[x][0]=0,f[x][1]=val[x];
for (int i=ls[x];i>0;i=g[i].next)
{
int y=g[i].y;
dfs(y,x);
for (int j=1;j<=size[x]+size[y];j++) tmp[j]=f[x][j];
for (int j=1;j<=size[x];j++)
{
for (int k=0;k<=size[y];k++)
{
tmp[j+k]=max(tmp[j+k],f[x][j]+f[y][k]);
}
}
size[x]+=size[y];
for (int j=1;j<=size[x];j++) f[x][j]=max(f[x][j],tmp[j]);
}
}
int main()
{
scanf("%d%d",&m,&n);
m++;
for (int i=1;i<=n;i++)
{
scanf("%d%d%d",&b[i],&a[i],&x);
add(x,i);
}
l=0,r=1e8;
while (r-l>esp)
{
mid=(l+r)/2;
for (int i=1;i<=n;i++) val[i]=(double)a[i]-b[i]*mid;
for (int i=0;i<=n;i++)
{
for (int j=0;j<=m;j++) f[i][j]=-0x3f3f3f3f;
}
dfs(0,0);
if (f[0][m]>=0) l=mid,ans=mid;
else r=mid;
}
printf("%.3lf",ans);
}