题目大意:有一棵$n(nleqslant100)$个点的树,每个点有两个权值$a,b$,要求选择一个$m$个点的连通块$S$,最大化$dfrac{sumlimits_{iin S}a_i}{sumlimits_{iin S}b_i}$
题解:$01$分数规划,这一类的问题可以二分答案来做,二分这个值,然后把第$i$个点的权值变为$a_i-b_imid$,跑一遍树形$DP$,$f_{i,j}$表示以第$i$个点为根,连通块大小为$j$的最大值。看答案是否大于$0$,是则把答案变大,否则缩小答案
卡点:做背包时做反了,$01$背包变成完全背包,精度不够。
C++ Code:
#include <algorithm> #include <cstdio> #include <cstring> #define maxn 111 const double eps = 1e-3; int head[maxn], cnt; struct Edge { int to, nxt; } e[maxn << 1]; inline void addedge(int a, int b) { e[++cnt] = (Edge) { b, head[a] }; head[a] = cnt; e[++cnt] = (Edge) { a, head[b] }; head[b] = cnt; } int n, m; int a[maxn], b[maxn]; double w[maxn], f[maxn][maxn], res; inline void chkmax(double &a, double b) { if (a < b) a = b; } void dfs(int u, int fa = 0) { f[u][0] = 0, f[u][1] = w[u]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (v != fa) { dfs(v, u); for (int j = m; j; --j) for (int k = 0; k < j; ++k) chkmax(f[u][j], f[u][j - k] + f[v][k]); } } chkmax(res, f[u][m]); } int main() { scanf("%d%d", &n, &m); m = n - m; for (int i = 1; i <= n; ++i) scanf("%d", a + i); for (int i = 1; i <= n; ++i) scanf("%d", b + i); for (int i = 1, a, b; i < n; ++i) { scanf("%d%d", &a, &b); addedge(a, b); } double l = 0, r = 10000; while (l + eps < r) { const double mid = (l + r) / 2; memset(f, 0xc2, sizeof f); res = **f; for (int i = 1; i <= n; ++i) w[i] = a[i] - b[i] * mid; dfs(1); if (res >= 0) l = mid; else r = mid; } printf("%.1lf ", l); return 0; }