基本0/1分数规划
给定一系列整数a1,a2…,ana_1,a_2 \ldots,a_na1,a2…,an以及b1,b2…,bnb_1,b_2 \ldots, bnb1,b2…,bn,求解一组xi(i<=i<=n,xi=1或0)x_i (i <= i <= n, x_i = 1或0)xi(i<=i<=n,xi=1或0) 使得下式最大化
∑i=1nai∗xi∑i=1nbi∗xi \frac{ \sum_{i=1}^{n} {a_i * x_i}}{\sum_{i=1}^{n} {b_i * x_i}} ∑i=1nbi∗xi∑i=1nai∗xi
在值域内二分答案,判断是否存在一组解xix_ixi,使得
∑i=1nai∗xi∑i=1nbi∗xi>=mid∑i=1nai∗xi−mid∗∑i=1nbi∗xi>=0∑i=1nxi∗(ai−mid∗bi)>=0
\frac{ \sum_{i=1}^{n} {a_i * x_i}}{\sum_{i=1}^{n} {b_i * x_i}} >= mid \\
\sum_{i=1}^{n}{a_i * x_i} - mid*\sum_{i=1}^{n}{b_i * x_i} >= 0 \\
\sum_{i=1}^{n}{x_i*(a_i - mid*b_i)} >= 0
∑i=1nbi∗xi∑i=1nai∗xi>=midi=1∑nai∗xi−mid∗i=1∑nbi∗xi>=0i=1∑nxi∗(ai−mid∗bi)>=0
若ai−mid∗bi>=0a_i - mid*b_i >= 0ai−mid∗bi>=0就令xi=1x_i=1xi=1,否则为000
#include <cstdio>
#include <iostream>
#include <algorithm>
#define eps 1e-7
#define maxn 1005
using namespace std;
double ans, sum;
double a[maxn], b[maxn], tmp[maxn];
bool check(double x, int n, int k)
{
double tol = 0.0;
for (int i = 0; i < n; ++i) tmp[i] = a[i] - x*b[i];
sort(tmp, tmp+n, greater<double>());
for (int i = 0; i < n-k; ++i) tol += tmp[i];
if (tol >= 0) return true;
else return false;
}
int main()
{
int n, k;
while (~scanf("%d %d", &n, &k)) {
if (n+k == 0) break;
sum = ans = 0;
for (int i = 0; i < n; ++i) scanf("%lf", &a[i]);
for (int i = 0; i < n; ++i) scanf("%lf", &b[i]);
double l = 0.0, r = 1.0, mid;
while (l <= r) {
mid = (l+r) / 2.0;
if (check(mid, n, k)) {
l = mid + eps;
ans = mid;
}
else r = mid - eps;
}
printf("%.0f\n", 100*ans);
}
return 0;
}
最优比率生成树
我们设一个顶点数为nnn,边数为mmm的无环连通图GGG,其中costicost_icosti表示第iii条边花费的代价,valueivalue_ivaluei表示第iii条边的价值,现在求这个图的一个生成树,使得这棵树的总花费与总价值比值最小。
设 xi=1or0x_i = 1 or 0xi=1or0 表示边eie_iei是否属于生出树,则有
r=∑i=1mvaluei∗xi∑i=1mcosti∗xiz=∑i=1mvaluei∗xi−r∗∑i=1mcosti∗xi
r = \frac {\sum_{i=1}^{m}{value_i*x_i}}{\sum_{i=1}^{m}{cost_i*x_i}} \\
z = \sum_{i=1}^{m}{value_i*x_i} - r*\sum_{i=1}^{m}{cost_i*x_i}
r=∑i=1mcosti∗xi∑i=1mvaluei∗xiz=i=1∑mvaluei∗xi−r∗i=1∑mcosti∗xi
可得到z(r)z(r)z(r)单调递减,z(max(r))=0z(max(r)) = 0z(max(r))=0,问题就变成了求z(r)z(r)z(r)的零点
运用DinkelbachDinkelbachDinkelbach算法
不去二分答案,而是先随便给定一个答案,然后根据更优的解max(z(r))max(z(r))max(z(r))对应直线的横截距,不断移动答案,逼近最优解
一般将rrr初始化为000
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
#define ABS(x) ((x) > 0 ? (x) : (-(x)))
using namespace std;
const double eps = 1e-5;
const int MAXN = 1010;
const double MAX_INT = (1 << 29) / 1.0;
struct Point{
double x, y, h;
double CalDis(const Point &a){
return sqrt((a.x - x) * (a.x - x) + (a.y - y) * (a.y - y));
}
double CalHig(const Point &a){
return ABS(h - a.h);
}
};
int n;
int pre[MAXN];
Point a[MAXN];
double mp[MAXN][MAXN];
double dis[MAXN][MAXN];
double cost[MAXN][MAXN];
bool vis[MAXN];
double dist[MAXN];
double prim(){
int s = 0;
memset(vis, false, sizeof(vis));
memset(pre, 0, sizeof(pre));
fill(dist, dist + MAXN, MAX_INT);
vis[s] = true;
dist[s] = 0.0;
int mip = s;
double mi = 0.0, dissum = 0.0, costsum = 0.0;
for(int i = 1; i < n; i++){
for(int j = 0; j < n; j++)
if(!vis[j] && dist[j] > mp[mip][j]){
dist[j] = mp[mip][j];
pre[j] = mip;
}
mi = MAX_INT;
for(int j = 0; j < n; j++)
if(!vis[j] && mi > dist[j]) mi = dist[j], mip = j;
vis[mip] = true;
dissum += dis[mip][pre[mip]];
costsum += cost[mip][pre[mip]];
}
return costsum / dissum;
}
inline void change(double mid){
for(int i = 0; i < n; i++)
for(int j = i + 1; j < n; j++)
mp[i][j] = mp[j][i] = cost[i][j] - mid * dis[i][j];
}
double solve(){
double last = 0.0, cur = -1.0;
while(ABS(cur - last) > eps){
last = cur;
change(last);
cur = prim();
}
return last;
}
int main(){
while(scanf("%d", &n) != EOF){
if(!n) break;
for(int i = 0; i < n; i++)
scanf("%lf%lf%lf", &a[i].x, &a[i].y, &a[i].h);
for(int i = 0; i < n; i++)
for(int j = i + 1; j < n; j++){
dis[i][j] = dis[j][i] = a[i].CalDis(a[j]);
cost[i][j] = cost[j][i] = a[i].CalHig(a[j]);
}
for(int i = 0; i < n; i++)
dis[i][i] = cost[i][i] = 0.0;
double ans = solve();
printf("%.3f\n", ans);
}
return 0;
}