POJ 2976 Dropping tests & 分数规划讲解



http://poj.org/problem?id=2976

题目要去掉一些的a[i],b[i]对,使得sigma(a[i])/sigma(b[i])最大。是一个典型的分数规划问题。

01分数规划问题

所谓的01分数规划问题就是指这样的一类问题,给定两个数组,a[i]表示选取i的收益,b[i]表示选取i的代价。如果选取i,定义x[i]=1否则x[i]=0。每一个物品只有选或者不选两种方案,求一个选择方案使得R=sigma(a[i]*x[i])/sigma(b[i]*x[i])取得最值,即所有选择物品的总收益/总代价的值最大或是最小。

分析过程

数学分析中一个很重要的方法就是分析目标式,这样我们来看目标式:R=sigma(a[i]*x[i])/sigma(b[i]*x[i])。

先定义一个函数F(L):=sigma(a[i]*x[i])-L*sigma(b[i]*x[i]),显然这只是对目标式的一个简单的变形。分离参数,得到F(L):=sigma((a[i]-L*b[i])*x[i])。这时我们就会发现,如果L已知的话,a[i]-L*b[i]就是已知的,当然x[i]是未知的。记d[i]=a[i]-L*b[i],那么F(L):=sigma(d[i]*x[i])。这个函数与目标式的关系非常的密切,L就是目标式中的R,最大化R也就是最大化L。F的值是由两个变量共同决定的,即方案X和参数L。对于一个确定的参数L来说,方案的不同会导致对应的F值的不同。假设已知在存在一个方案X使得F(L)>0即F(L)=sigma(a[i]*x[i])-L*sigma(b[i]*x[i])>0即sigma(a[i]*x[i])/sigma(b[i]*x[i])>L也就是说,如果一个方案使得F(L)>0说明了这组方案可以得到一个比现在的L更优的一个L,既然有一个更优的解。显然,d数组是随着L的增大而单调减的。也就是说,存在一个临界的L使得不存在一种方案,能够使F(L)>0. 我们猜想,这个时候的L就是我们要求的最优解。之后更大的L值则会造成无论任何一种方案,都会使F(L)<0.类似于上面的那个变形,F(L)<0是没有意义的,因为这时候的L是不能够被取得的。当F(L)=0使,对应方案的R值恰好等于此时的L值。 综上,函数F(L)有这样的一个性质:在前一段L中可以找到一组对应的X使得F(L)>0,这就提供了一种证据,即有一个比现在的L更优的解,而在某个L值使,存在一组解使得F(L)=0,且其他的F(L)<0,这时的L无法继续增大,即这个L就是我们期望的最优解,之后的L会使得无论哪种方案都会造成F(L)<0.而我们已经知道,F(L)<0是没有任何意义的,因为此时的L值根本取不到。 

采用方法

根据这样的一段性质,很自然的就可以想到二分L值,然后验证是否存在一组解使得F(L)>0,有就移动下界,没有就移动上界。所有的01分数规划都可以这么做,唯一的区别就在于求解时的不同——因为每一道题的限制条件不同,并不是每一个解都是可行解的。比如在普通的数组中,你可以选取1、2、3号元素,但在生成树问题中,假设1、2、3号元素恰好构成了一个环,那就不能够同时选择了,这就是需要具体问题,具体分析的部分。

注意

分数规划的二分搜索和普通的二分搜索有一定的区别,详见代码中的注释。

AC_CODE:

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>

using namespace std;

struct node{
    double a,b,x;
}in[1010];

bool cmp(const node &A, const node &B){
    return A.x > B.x;
}

int main(){
//    freopen("in.txt", "r", stdin);
    int n,k;
    while(scanf("%d%d", &n, &k) != EOF){
        if(!n && !k)    break;
        for(int i=0; i<n; i++)  scanf("%lf",&in[i].a);
        for(int i=0; i<n; i++)  scanf("%lf",&in[i].b);
        double left = 0, right = 100, mid;
        while(right - left > 1e-4){//由于左右边界是浮点型,所以将所要求的精度作为循环结束的条件
            mid = (left + right) / 2;
            double sum = 0;
            for(int i=0; i<n; i++)
                in[i].x = 100 * in[i].a - mid * in[i].b;
            sort(in, in+n, cmp);
            for(int i=0; i<n-k; i++)
                sum += in[i].x;
            if(sum > 0){//不能写left = mid + 1的原因是left是浮点型数据,让mid+0.99情何以堪呢^^.
                left = mid;
            }
            else
                right = mid;
        }
        printf("%.0lf\n",mid);
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值