题目大意:
给定一棵树,nnn个节点。每个节点有aia_iai和bib_ibi两个值。要求选出若干节点,保证某个节点被选,他的父亲一定被选。
使
∑i=1kai∑i=1kbi\frac{\sum_{i=1}^{k}a_i}{\sum_{i=1}^{k}b_i}∑i=1kbi∑i=1kai最大化。
n≤1000n≤1000n≤1000
分析:
考虑分数规划,二分一个midmidmid,
当midmidmid合法时,有
∑i=1kai∑i=1kbi≥mid\frac{\sum_{i=1}^{k}a_i}{\sum_{i=1}^{k}b_i}≥mid∑i=1kbi∑i=1kai≥mid
即
∑i=1kai−mid∗∑i=1kbi≥0\sum_{i=1}^{k}a_i-mid*\sum_{i=1}^{k}b_i≥0i=1∑kai−mid∗i=1∑kbi≥0
也就是
∑i=1kai−mid∗bi≥0\sum_{i=1}^{k}a_i-mid*b_i≥0i=1∑kai−mid∗bi≥0
把点的权值设为ai−mid∗bia_i-mid*b_iai−mid∗bi,考虑树上dp。设f[i][j]f[i][j]f[i][j]表示iii的子树中选jjj个的最大值,看是否≥0。
转移要使用枚举sizesizesize 的方法,dp复杂度是O(n2)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);
}