题目大意
一棵树,有
n+1
个节点,根编号为
0
。
每个非根节点都有两个权值
要求选择
K+1
个节点,最大化
∑pi∑si
并且所选节点一定包括根,并且如果选择了节点 x(x≠0) 那么 x 的父亲
1≤K≤n≤2500,0<si,pi≤104,0≤ri<i
题目分析
看见最大化的分式,显然要使用
01
分数规划。
二分答案转化为判定性问题,计算是否存在权值和小于零的选择方案。
由于这里有树形依赖关系,那我们需要用一个神奇的方法做树形
dp
。
注意到
x
的子树中任一节点能选择当且仅当
所以我们可以按照
DFS
序列来
dp
,令
fi,j
表示待处理节点在
DFS
序中排名为
i
,已经选择了
∙
如果不选择
p
,那么就要跳过其子树,转移到
这个
dp
很巧妙,利用了树形依赖的性质。
时间复杂度
O(nKlog2(ns))
。
代码实现
#include <iostream>
#include <climits>
#include <cfloat>
#include <cstdio>
#include <cctype>
using namespace std;
typedef double db;
int read()
{
int x=0,f=1;
char ch=getchar();
while (!isdigit(ch)) f=(ch=='-')?-1:f,ch=getchar();
while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
}
const db INF=DBL_MAX/3;
const db EPS=0.0001;
const int N=2550;
const int K=2550;
int fa[N],last[N],tov[N],next[N],s[N],p[N],size[N],id[N];
int n,k,tot,idx,sum;
db f[N][K],v[N];
db ans;
void insert(int x,int y)
{
tov[++tot]=y,next[tot]=last[x],last[x]=tot;
}
void dfs(int x)
{
size[id[++idx]=x]=1;
for (int i=last[x],y;i;i=next[i])
dfs(y=tov[i]),size[x]+=size[y];
}
bool check(db now)
{
for (int i=2;i<=n+1;i++)
{
v[i]=s[id[i]]*now-p[id[i]];
for (int j=0;j<=k;j++)
f[i][j]=INF;
}
f[2][0]=0;
for (int i=2;i<=n;i++)
{
for (int j=0;j<=k&&j<=i-2;j++)
{
if (f[i][j]<f[i+size[id[i]]][j]) f[i+size[id[i]]][j]=f[i][j];
if (j<k&&f[i+1][j+1]>f[i][j]+v[i]) f[i+1][j+1]=f[i][j]+v[i];
}
}
return f[n+1][k]>0;
}
double binary_search()
{
db l=0,r=sum,mid,ret;
while (l+EPS<r)
{
mid=(l+r)/2;
if (check(mid)) r=mid;
else ret=mid,l=mid;
}
return ret;
}
int main()
{
freopen("team.in","r",stdin),freopen("team.out","w",stdout);
k=read(),n=read();
for (int i=1;i<=n;i++)
{
s[i+1]=read(),sum+=(p[i+1]=read()),fa[i+1]=read();
insert(++fa[i+1],i+1);
}
n++;
dfs(1);
ans=binary_search();
printf("%.3lf\n",ans);
fclose(stdin),fclose(stdout);
return 0;
}