01分数规划入门

 

 

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;
}

参考: https://www.cnblogs.com/captain1/p/9929128.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值