01分数规划 基础教学篇
01规划问题大概是给你n件物品,每个物品有价值P和体积V两个属性,让你选择其中k件,使得这k件物品的价值和除以体积和最大。
我们可以设X[i]=0/1,如果X[i]==0说明第i件物品物品不选,如果X[i]==1说明第i件物品要选。
那么我们要求的k件物品的价值和除以体积和R=sigma(P[i]*X[i])/sigma(V[i]*X[i]);
我们再定义一个函数f(L)=sigma(P[i]*X[i])-L*sigma(V[i]*X[i])=sigma((P[i]-L*V[i])*X[i]);设d[i]=P[i]-L*V[i];则f(L)=sigma(d[i]*X[i]);
假设f(L)>0,也就是sigma(P[i]*X[i])-L*sigma(V[i]*X[i])>0,变形后就是sigma(P[i]*X[i])/sigma(V[i]*X[i])>L,那么01分数规划问题就可以转化为找到最大的L使得f(L)>0;
由于d[i]=P[i]-L*V[i]是随着L增大而递减的,因此f(L)=sigma(d[i]*X[i])也是线性的,可以直接二分寻找这个L。
那么如何判断f(L)>0呢?我们处理出来d数组后,要在里面选择其中k个使得他们的和大于0,那么就要找前k大的数字加起来,所以排下序再记录下和就行了。
找到L后,L也是我们所要求的答案。
这里有道模板题: http://poj.org/problem?id=2976
参考代码:
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #include <vector> #include <queue> #include <string> #include <stack> #include <map> #include <set> #include <bitset> #define b first #define a second #define clr(u,v); memset(u,v,sizeof(u)); #define in() freopen("data","r",stdin); #define out() freopen("ans","w",stdout); #define Clear(Q); while (!Q.empty()) Q.pop(); #define pb push_back using namespace std; typedef long long ll; typedef pair<double, double> pdd; typedef pair<int, int> pii; const int maxn = 1e4 + 10; const int INF = 0x3f3f3f3f; const double eps = 1e-6; pdd N[maxn]; int n, k; double d[maxn]; bool f(double L) { double sum = 0; for (int i = 0; i < n; i++) d[i] = N[i].a - L * N[i].b; sort(d, d + n); for (int i = n - 1; i >= n - k; i--) sum += d[i]; return sum > 0; } double solve(double L, double R) { double mid; while (R - L > eps) { mid = (L + R) / 2; if (f(mid)) L = mid; else R = mid; } return mid; } int main() { while(scanf("%d%d", &n, &k), n || k) { double Mx = 0; k = n - k; for (int i = 0; i < n; i++) scanf("%lf", &N[i].a); for (int i = 0; i < n; i++) { scanf("%lf", &N[i].b); Mx = max(Mx, N[i].a / N[i].b); } printf("%.0f\n", solve(0, Mx) * 100); } return 0; }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~