题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4753
题解:
对于最终答案进行浮点二分,对于每次的
mid
m
i
d
,进行
dp
d
p
。
令
dp[i][j]
d
p
[
i
]
[
j
]
表示考虑到
dfs序
d
f
s
序
第
i
i
个结点及所在子树,已经选了 个结点的最大权值(
Σni=1(p[i]−cur∗s[i])
Σ
i
=
1
n
(
p
[
i
]
−
c
u
r
∗
s
[
i
]
)
),后面的那一个式子每次二分可以先预处理一下。
注意到我们进行
dp
d
p
的时候需要按照字典序
dp
d
p
,所以可以先在二分之前
dfs
d
f
s
一下。
接下来问题就变成了:要选若干个数,如果要选结点
i
i
则必须要选的左右祖先,这是一个依赖背包,每次
dp
d
p
的时候有两种情况:
(1)
(
1
)
第
i
i
个点选:
(2)
(
2
)
第
i
i
个点不选:
其中
to[i]
t
o
[
i
]
表示
dfs
d
f
s
序第
i
i
个点所在子树的序的最后一个结点,这个可以在一开始
dfs
d
f
s
的时候顺便预处理一下。
然后答案就是
dp[i][k+1]
d
p
[
i
]
[
k
+
1
]
是否存在一个
i
i
使得,注意到因为我这是用推的方法写
dp
d
p
的,所以答案就是
k+1
k
+
1
而不是
k
k
。
注意浮点数的精度,我用的是取到
1e−7
1
e
−
7
代码:
// by Balloons
#include <cstdio>
#include <cstring>
#include <vector>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() puts("okkkkkkkk")
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
using namespace std;
typedef long long LL;
const int inf = 1 << 30;
const double eps=1e-7;
const int maxn=2505;
int k,n;
int s[maxn],p[maxn],r[maxn];
vector<int>tree[maxn];
int dfn[maxn],dfs_clock=0,to[maxn];
// dp[i][j] 表示考虑到第 i 个结点及所在子树,已经选了 j 个结点的最大权值(sigma (p[i]-cur*s[i]))
double dp[maxn][maxn],d[maxn];
// 通过 dfs 预处理出 dfs序和 i 及所在子树能够最多到达的dfs序
void dfs(int now){
dfn[now]=dfs_clock++;
for(int i=0;i<tree[now].size();i++){
int u=tree[now][i];
dfs(u);
}
to[dfn[now]]=dfs_clock;
}
int check(double cur){
// dfs序为 i 的人所对应的权值
for(int i=1;i<=n;i++)d[dfn[i]]=1.0*p[i]-cur*s[i];
// 0号结点一定需要选
for(int i=1;i<=n+1;i++)
for(int j=0;j<=k+1;j++)dp[i][j]=-1e9;
for(int i=0;i<=n;i++){
for(int j=0;j<=min(i,k);j++){
// 当前结点选,如果当前不是所在子树dfs序的最后一个结点,且父亲结点选过,就更新(加上权值)
if(to[i]!=i&&dp[i][j]!=-1e9)dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]+d[i]);
// 当前结点不选,就是直接更新到下一个非i子树的结点
if(dp[i][j]!=-1e9)dp[to[i]][j]=max(dp[to[i]][j],dp[i][j]);
}
}
// 判断,如果有一种选法可以满足条件,就ok
for(int i=0;i<=n+1;i++)
if(dp[i][k+1]>=eps)return 1;
return 0;
}
int main(){
scanf("%d%d",&k,&n);
for(int i=1;i<=n;i++){
scanf("%d%d%d",&s[i],&p[i],&r[i]);
tree[r[i]].push_back(i);
}
dfs(0);
double l=0.0,r=10000.0,ans;
while(l+eps<=r){
double mid=(l+r)/2.0;
if(check(mid)){
ans=mid;
l=mid;
}else r=mid;
}
printf("%.3f\n",ans);
return 0;
}