题解-LibreOJ-2071最佳团体

题目链接(Luogu)

题目链接(LibreOJ)

题目概括重金征集中~

0/1 分数规划的基本模型:给定整数 $a_1,a_2,…,a_n$ 以及 $b_1,b_2,…,b_n$,求一组解 $x_i$($x_i$ 的取值为 0 或 1),使下式最大化:
$$
\frac{\Sigma a_ix_i}{\Sigma b_ix_i}
$$
在本题中,${a_n}$ 对应着战斗值, ${b_n}$ 对应着招募费用。题目要求从中选出若干名选手(即将相应的 $x_i$ 赋为 1),使得上式最大化。

值得注意的是,在本题中 $x_i$ 的取值同样有限制:如果 $x_i=1$,则一定有 $x_{R[i]}=1$ 或$R[i]=0$。

接下来考虑上式的计算。我么们不妨任意猜测一个值 $mid$ ,如果 $\frac{\Sigma a_i*x_i}{\Sigma b_i*x_i} \ge mid$ ,即 $mid$ 比我们要求的最大值要小,则可以推出 $\Sigma (a_i-mid*b_i)*x_i\ge 0$。因此,我们只需判定 $\Sigma (a_i-mid*b_i)*x_i$ 是否大于等于 0,即可进一步缩小 $mid$ 的范围。

综上所述,我们可以二分答案(实数)。当二分的值为 $mid$ 时,计算 $\Sigma (a_i-mid*b_i)*x_i$ 的最大值,检查最大值是否非负。若非负,则令 $l=mid$;否则令 $r=mid$。当二分停止时,就得到了 0/1 分数规划问题的解。

本题的另一关键点在于计算 $\Sigma (a_i-mid*b_i)*x_i$ 的最大值。本题可以采用背包类树形 DP 实现,具体内容不再赘述。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <bits/stdc++.h>
using namespace std;

const float eps = 1e-4;

struct Edge {
int v, nxt;
}edge[2505];
int cnt, head[2505];

inline void AddEdge(int u, int v) {
edge[++cnt].v = v;
edge[cnt].nxt = head[u];
head[u] = cnt;
}

int K, N;
int a[2505], b[2505];
float f[2505][2505];
int size[2505];

float delta;

inline void dfs(int u) {
size[u] = 1;
for (int i = head[u]; i; i = edge[i].nxt) {
dfs(edge[i].v);
size[u] += size[edge[i].v];
}
}

inline void dp(int u) {
float w = 1.0 * a[u] - 1.0 * delta * b[u];
for (int i = 1; i <= K; ++i)
f[u][i] = -1e9;
for (int i = head[u]; i; i = edge[i].nxt) {
int v = edge[i].v;
dp(v);
for (int i = min(K, size[u]); i >= 1; --i)
for (int j = 0; j <= min(size[v], i); ++j)
f[u][i] = max(f[u][i], f[u][i - j] + f[v][j]);
}
if (u != 0) {
for (int i = min(K, size[u]); i >= 1; --i)
f[u][i] = f[u][i - 1] + w;
f[u][1] = w;
}
}

int main() {
scanf("%d%d", &K, &N);
for (register int i = 1; i <= N; ++i) {
int r;
scanf("%d%d%d", &b[i], &a[i], &r);
AddEdge(r, i);
}
dfs(0);
float l = 0, r = 10000;
while (r - l > eps) {
float mid = (l + r) / 2;
delta = mid;
dp(0);
if (f[0][K] < 0) r = mid;
else l = mid;
}
printf("%.3f", l);
return 0;
}

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×