BZOJ 4753 JSOI2016 最佳团体
BZOJ 4753 JSOI2016 最佳团体
题目描述
JSOI信息学代表队一共有N名候选人,这些候选人从1到N编号。方便起见,JYY的编号是0号。每个候选人都由一位
编号比他小的候选人Ri推荐。如果Ri=0则说明这个候选人是JYY自己看上的。为了保证团队的和谐,JYY需要保证,
如果招募了候选人i,那么候选人Ri"也一定需要在团队中。当然了,JYY自己总是在团队里的。每一个候选人都有
一个战斗值Pi",也有一个招募费用Si"。JYY希望招募K个候选人(JYY自己不算),组成一个性价比最高的团队。
也就是,这K个被JYY选择的候选人的总战斗值与总招募总费用的比值最大。
解题思路
首先考虑分数规划,二分这个最大的比值。
\[\frac{\sum{p}}{\sum s} \geq mid\\
\sum p \geq mid*\sum s\\
0\geq mid *\sum s-\sum p
\]
于是我们要最小化这个式子,dp即可。
典型的树形dp。(原来我是用的树形dp是比较少细节的,不会那么容易退化成\(n^3\))
注意一下,初始化的时候除了0号节点,选0个点都是正无穷的代价,而在这次dfs结束的时候,要把0个节点的代价设为0,表示以后可以丢掉这个连通块。
PS.不要使用long double 慢到怀疑人生。
#include<bits/stdc++.h>
using namespace std;
#define db double
#pragma GCC optimize(2)
const int N = 2500 + 11;
int n, K, sz[N];
int wei[N], cst[N], fa[N];
int head[N], nex[N], to[N], size;
db W[N], C[N], f[N][N], tmp[N];
void add(int x, int y){
to[++size] = y;
nex[size] = head[x];
head[x] = size;
}
void dfs(int u) {
if (u) f[u][1] = C[u] - W[u];
else f[u][0] = 0;
sz[u] = 1;
for (int i = head[u]; i; i = nex[i]) {
int v = to[i];
dfs(v);
for (int j = 0; j <= sz[u] && j <= K; j++) {
for (int k = 0; k <= sz[v] && j + k <= K; k++) {
tmp[j+k] = min(tmp[j+k], f[u][j] + f[v][k]);
}
}
sz[u] += sz[v];
for (int j = 0; j <= K; j++) {
f[u][j] = tmp[j];
tmp[j] = 1e9;
}
}
f[u][0] = 0;
}
int main(){
//freopen("4753.in", "r", stdin);
//freopen("4753.out", "w", stdout);
cin>>K>>n;
for(int i = 1;i <= n; i++){
scanf("%d%d%d", &cst[i], &wei[i], &fa[i]);
W[i] = wei[i];
add(fa[i], i);
}
for(int i = 0;i <= K; i++)tmp[i] = 1e9;
db l = 0, r = 1e4, res = 0;
while(r - l > 1e-4){
db mid = (l + r) / 2.0;
for(int i = 1;i <= n; i++){
C[i] = 1.0 * mid * cst[i];
}
for(int u = 0;u <= n; u++)
for(int i = 0;i <= K; i++)f[u][i] = 1e9;
dfs(0);
//printf("mid=%.3lf f=%.3lf\n", mid, f[0][K]);
//cerr<<1.0 * clock() / 1000 <<endl;
if(f[0][K] <= 0.0){
res = mid; l = mid + 1e-5;
}
else r = mid - 1e-5;
//return 0;
}
printf("%.3lf\n", res);
return 0;
}