POJ 2976 -- 0/1分数规划

本文深入解析了基本0/1分数规划问题,通过实例演示如何利用二分法寻找最优解,并探讨了最优比率生成树问题,介绍了Dinkelbach算法在求解这类问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基本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&lt;=i&lt;=n,xi=1或0)x_i (i &lt;= i &lt;= n, x_i = 1或0)xi(i<=i<=n,xi=10) 使得下式最大化
∑i=1nai∗xi∑i=1nbi∗xi \frac{ \sum_{i=1}^{n} {a_i * x_i}}{\sum_{i=1}^{n} {b_i * x_i}} i=1nbixii=1naixi

在值域内二分答案,判断是否存在一组解xix_ixi,使得
∑i=1nai∗xi∑i=1nbi∗xi&gt;=mid∑i=1nai∗xi−mid∗∑i=1nbi∗xi&gt;=0∑i=1nxi∗(ai−mid∗bi)&gt;=0 \frac{ \sum_{i=1}^{n} {a_i * x_i}}{\sum_{i=1}^{n} {b_i * x_i}} &gt;= mid \\ \sum_{i=1}^{n}{a_i * x_i} - mid*\sum_{i=1}^{n}{b_i * x_i} &gt;= 0 \\ \sum_{i=1}^{n}{x_i*(a_i - mid*b_i)} &gt;= 0 i=1nbixii=1naixi>=midi=1naiximidi=1nbixi>=0i=1nxi(aimidbi)>=0
ai−mid∗bi&gt;=0a_i - mid*b_i &gt;= 0aimidbi>=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=1mcostixii=1mvalueixiz=i=1mvalueixiri=1mcostixi
可得到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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值