描述:
有n个物品,单个物品的质量为w(weigh),价值为v(value),要从n个物品中选取k个,求再所有的选取方案中,能获得的最大单位质量价值。也就是k个物品总的价值V / 总的质量W。保留两位小数。
说明:
一般最先想到的方法是把每个物品按照单位价值排序,从大到小贪心进行选取。但这种方法是不正确的。举一个反例就好了。
比如说:n = 3, k = 2,(w, v) = {(2,2),(5,3),(2,1)},按照之前的策略应该选第一和第二物品,得到的平均价值为5/7 = 0.714。
但如果我们选择第一和第三个物品的话,结果就是3/4 = 0.75。所以说要想别的办法。
定义条件 C(x):= 可以选择使得单位重量的价值不小于x
原问题变成了求满足C(x)的最大x的问题。如何判断C(x)为真呢,假设我们选了k个物品,组成的集合为S,那么它们的单位质量价值是 Σv / Σw。
因此就变成了判断是否存在S满足条件 Σv / Σw >= x,变形得到 Σ(vi - x * wi) >= 0 。
因此,对vi - x * wi 的值进行排序,贪心的进行选取,最后问题变成了
C(x):((vi - x * wi) 从大到小排列中前k个和不小于0) 每次判断排序依次复杂度O(nlogn)。
完整实现代码如下:
#include <iostream> #include <algorithm> using namespace std; const int MAX_N = 1000; const double MAX_X = 1000000.0; int n, k; int w[MAX_N], v[MAX_N]; double y[MAX_N]; // v - x * w bool C(double x) { for (int i = 0; i < n; i++) y[i] = v[i] - x * w[i]; sort(y, y + n); // 计算y数组中从大到小前k个数的和 double sum = 0.0; for (int i = 0; i < k; i++) sum += y[n - 1 - i]; return sum >= 0; } int main() { cin >> n >> k; for (int i = 0; i < n; i++) cin >> w[i] >> v[i]; double lb = 0.0, ub = MAX_X; for (int i = 0; i < 100; i++) { double mid = (lb + ub) / 2; if (C(mid)) lb = mid; else ub = mid; } printf("%.2f\n", ub); }
结果:
ps:这个主要在《挑战程序设计竞赛》上看到的,感觉非常巧妙。搬上来跟大家分享下。也强烈推荐这本书。