[洛谷P1642]规划

题目大意:有一棵$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;
}

  

原文地址:https://www.cnblogs.com/Memory-of-winter/p/10352798.html