01分数规划入门
01分数规划,简单的来说,就是有一些二元组(si,pi),从中选取一些二元组,使得∑si / ∑pi最大(最小)。
这种题一类通用的解法就是,我们假设x = ∑si / ∑pi的最大(小)值,那么就有x * ∑pi = ∑si ,即∑si - x * ∑pi= 0。也就是说,当某一个值x满足上述式子的时候,它就是要求的值。我们可以想到枚举……不过再想想,这个可以二分答案。
所以我们直接二分答案,当上述式子>0,说明答案小了,<0则说明答案大了,这样计算即可。
这是一种解决问题的方法,具体应该怎么做我们要看题来分析。
01分数规划有这样几种基本的题型(当然还有很多别的……暂时不在juruo的考虑范围内)
1.01分数规划
2.最优比率生成树
3.最优比率生成环
(当然还有什么最优比率最小割等等……不在juruo当前研究范围之内)
1.01分数规划。
基础题:poj2976
这个就是一开始说的这么一码事……选取n-k(原题是不选k)个物品使得比率最大。
我们考虑对上面的式子进行转化,之后我们只要给每个物品重新赋值为a - x*b,排序之后直接取前n-k大,判断这个值的正负性,然后按上面情况二分即可。
如果不大明白为什么是这么取得的,可以看这里(个人觉得很详细)
附上代码。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define ll long long
#define exp 0.000001
#define mid ((l+r)/2)
const int mx = 3e5+100;
ll ab(ll a) {return a<0?-a:a;}
int min(int a, int b) { return a<b?a:b;}
template<class T>T minn(T a, T b) {return min(a, b);}
template<class T, class ...R>T minn(T a, R... b) {return min(a, minn(b...));}
struct node {
double a, b, c;
}N[mx];
ll n, m;
bool cmp(node as, node bs) {
return as.c>bs.c;
}
bool z(double mk) {
for (int i = 0; i < n; ++i) {
N[i].c = N[i].a-N[i].b*mk;
}
sort(N,N+n,cmp);
double sum =0;
for (int i = 0; i < n-m; ++i) {
sum += N[i].c;
}
return sum >= 0.0;
}
int main() {
ll t;
while (scanf("%lld%lld", &n, &m),n+m) {
double h = (1<<30), sum = 0;
for (int i = 0; i < n; ++i) {scanf("%lf", &N[i].a);
sum += N[i].a;
}
for (int i = 0; i < n; ++i) {scanf("%lf", &N[i].b);
h = min(h, N[i].b);
}
double l = 0, r = sum/h, ans = mid;
while (r-l > exp) {
if (z(mid)) {
ans = mid;
l = mid+exp;
}
else r = mid-exp;
}
printf("%.0f\n", ans*100);
}
return 0;
}
2.最优比率生成树。
基础题:poj2728
简单描述就是,每一条路有一个花费p和一个权值s,要在图上选取一些边构成一棵生成树,求生成树的∑pi / ∑si最小值。
我们还是考虑套用上面的模板,其实这个和01分数规划很像的。只要对每条边重新赋一个值,因为这次我们要求的是花费比上权值,所以和上一道题反了过来,赋值是p-x * s。之后因为要求最小,直接求最小生成树即可。判断一下最小生成树的权值和,如果小于0说明答案设大了,否则说明答案设小了。
这道题是完全图,所以我们需要用prim求最小生成树。(突然发现我从来都是用kruskal都没用过prim),但是它的想法其实很简单,我们从任意一个点开始,之后选取一个最近的点,并且用这个点更新其他点的距离,之后再寻找一个更近的点,再去更新距离。它的复杂度是O(n2)的。
看一下代码。
const int mx = 3e3+100;
struct node {
int a, b;
double c, h, k;
}N[mx];
ll n, m;
bool vis[1010];
double C[mx][mx], H[mx][mx], ma[mx][mx], di[mx];
double prim() {
double sum = 0;
memset(vis, 0, sizeof(vis));
vis[0] = true;
for (int i = 1; i < n; ++i) di[i] = ma[0][i];
for (int i = 0; i < n; ++i) {
double d = 1e9;
int pos = -1;
for (int j = 1;j < n; ++j) if (!vis[j] && di[j] < d) {
d = di[j];
pos = j;
}
if (pos == -1) break;
sum += d;
vis[pos] = true;
for (int j = 1; j < n; ++j) if (ma[pos][j] < di[j]) di[j] = ma[pos][j];
}
return sum;
}
bool z(double mk) {
for (int i = 0; i < n; ++i) {
for (int j = i+1; j < n; ++j) {
ma[i][j] = ma[j][i] = H[i][j]-mk*C[i][j];
}
}
// printf("%f\n", prim());
return prim() <= 0.0;
}
double dis(node x, node y) {
return sqrt(1.0*(x.a-y.a)*(x.a-y.a)+1.0*(x.b-y.b)*(x.b-y.b));
}
int main() {
while (scanf("%lld", &n),n) {
for (int i = 0; i < n; ++i)
scanf("%d%d%lf", &N[i].a, &N[i].b, &N[i].h);
for(int i = 0; i < n; ++i) {
for (int j = i+1; j < n; ++j) {
C[i][j]= dis(N[i],N[j]);
H[i][j]= fabs(N[i].h-N[j].h);
}
}
double l = 0, r = 1e7, ans = mid;
while (r-l > exp) {
if (z(mid)) {
ans = mid;
r = mid-exp;
} else
l = mid+exp;
}
printf("%.3f\n", ans);
}
return 0;
}