[JSOI2016]最佳团体
读完题第一眼就是分数规划,求最大值,所有可以采用二分答案的方式解决。
Σ
i
=
1
n
p
i
Σ
i
=
1
n
s
i
>
m
i
d
\frac{{\Sigma_{i = 1}^{n}}p_i}{{\Sigma_{i = 1}^{n}}s_i} > mid
Σi=1nsiΣi=1npi>mid
则将该式化简可以得到
p
i
−
m
i
d
∗
s
i
>
0
p_i - mid * s_i > 0
pi−mid∗si>0
故可以将该题可以按照点权为
p
i
−
m
i
d
∗
s
i
>
0
p_i - mid * s_i > 0
pi−mid∗si>0的树,
R
i
R_i
Ri 为父节点 ,
i
i
i为子节点,根节点为0。
在完成建树之后如何计算最大值的计算?
在仔细读完题之后可以发现一个细节:如果招募了候选人
i
i
i,那么候选人
R
i
R_i
Ri也一定需要在团队中.
因此对于一个节点来说如果你要选择该节点,那么他的父亲节点也要被选择。
因此可采用树形dp
d
p
[
u
]
[
k
]
dp[u][k]
dp[u][k]表示以节点u为根节点的子树中选择k个节点所得到的最大值。
具体可以代码注释
代码如下
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2510;
const double eps = 1e-6;
int n, k;
struct man {
double s, p, r;
} M[N];
double w[N], dp[N][N];
int e[N << 1], ne[N << 1], h[N], idx;
int cnt;
int sze[N];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs(int u, int fa) {
dp[u][1] = w[u];
sze[u] = 1;
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if (j == fa)
continue;
dfs(j, u);
sze[u] += sze[j];
// m 表示当前节点的子树选择m个
// o 表示以j为根的子树选择j个
for (int m = min(sze[u], k + 1); m >= 1; m --) {
for (int o = 0; o <= min(sze[j], m - 1); o ++)
dp[u][m] = max(dp[u][m], dp[u][m - o] + dp[j][o]);
}
}
}
bool check(double mid) {
w[0] = 0;
for (int i = 0; i < N; i ++)
for (int j = 0; j < N; j ++)
dp[i][j] = -1e9;
for (int i = 1; i <= n; i ++)
w[i] = M[i].p - mid * M[i].s;
dfs(0, -1);
return dp[0][k + 1] >= 0;
}
void run() {
memset(h, -1, sizeof h);
cin >> k >> n;
for (int i = 1; i <= n; i ++) {
double s, p, r;
cin >> s >> p >> r;
M[i] = {s, p, r};
add(M[i].r, i);
}
double l = 0, r = 1e5;
while (r - l > eps) {
double mid = (l + r) / 2;
if (check(mid))
l = mid;
else
r = mid;
}
printf("%.3lf\n", l);
}
int main() {
ios_base::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
run();
return 0;
}