POJ 2976 Dropping tests & 分数规划讲解

来源:互联网 发布:平安淘宝信用卡 看电影 编辑:程序博客网 时间:2024/05/02 01:48


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