P4322 JSOI2016 最佳团体 - 洛谷 (luogu.com.cn)
每个候选人 依赖
,且
,所以依赖关系形成一棵以
为根的树。
题目要求:如果选 ,就必须选
。这就是典型的树形依赖 dp,选子节点必须先选父节点。
但题目要最大化的是 不是直接最大化
,所以不能直接背包,需要二分比值答案。

设计 dp 状态:dp[i][j] 表示从 dfs 序第 i 个位置开始,选 j 个节点的最大权值和。
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2510;
double S[N], P[N]; int R[N];
vector<int> G[N];
int dfn[N], tsp, id[N], siz[N];
int K, n;
double w[N], dp[N][N];
// dp[i][j] 表示从 dfs 序第 i 个位置开始,选 j 个节点的最大权值和
void dfs(int x) {
dfn[x] = tsp;
id[tsp] = x;
tsp ++;
siz[x] = 1;
for (int y : G[x]) {
dfs(y);
siz[x] += siz[y];
}
}
bool check(double x) {
for (int i = 1; i <= n; i ++) {
w[i] = P[i] - x * S[i];
}
w[0] = 0;
for (int i = 0; i <= n + 1; i ++) {
for (int j = 0; j <= K + 1; j ++) {
dp[i][j] = -1e18;
}
}
dp[n + 1][0] = 0;
for (int i = n; i >= 0; i --) { // 照 dfs 序从后往前遍历,也就是从叶子到根
int u = id[i]; // 当前 dfs 序对应的节点
for (int j = 0; j <= K + 1; j ++) {
if (j >= 1) {
dp[i][j] = max(dp[i][j], dp[i + 1][j - 1] + w[u]);
// 当选了 u 节点,就可以从 u 的第一个孩子节点 i + 1 处转移 dp
}
if (i + siz[u] <= n + 1) {
dp[i][j] = max(dp[i][j], dp[i + siz[u]][j]);
// 不选 u 节点,就只能从第一个不是 u 节点孩子转移,即跳过 u 的整个子树
}
// 叶子节点没有孩子的特殊情况,上面两种分讨也包含了
}
}
/*
假设某个叶子节点 v 被选中了
在 dfs 序中,v 一定位于它所有祖先的子树区间内
要从 dfs 序的起点走到 v,必须依次经过它的父节点、祖父节点......直到根
在递推过程中,如果某个祖先 u 没有被选,那么根据情况一,
我们会直接跳过 u 的整个子树,根本不会进入 v 所在的区间
因此 v 不可能被选中
*/
return (dp[0][K + 1] >= 0); // 加上 0 就多选一个
}
int main () {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> K >> n;
for (int i = 1; i <= n; i ++) {
cin >> S[i] >> P[i] >> R[i];
G[R[i]].push_back(i);
}
tsp = 0;
dfs(0);
double l = 0, r = 10000;
// 小数点后三位精确,也就是 10000000 的精确度
// 2 ^ 50 完全够
for (int i = 0; i <= 50; i ++) {
double mid = (l + r) / 2;
if (check(mid)) {
l = mid;
}
else {
r = mid;
}
}
cout << fixed << setprecision(3) << l << "\n";
return 0;
}