POJ 2976--Dropping tests(Dinkelbach求0/1规划)

来源:互联网 发布:excel怎么汇总数据 编辑:程序博客网 时间:2024/06/03 23:14

题意:类似于GPA求解的过程,给定两个规模为N的数组A,B,其中A的数小于等于相同位置上B的数,请选择k个位置,使A中数的和除以B中数的和达到最大。

题解:类似于背包,每个位置有0,1两种选择。

  • 设N维列向量X,其中只包含0,1两种元素。则A*X就等于分子的和,B*X就相等于与分母的和。
  • 设相除的结果为L,即L = (A*X)/(B*X)。
  • 我们设一关于L的函数,Z = A*X-(B*X)*L,其中X只含有k个1,且在给定L的情况下使Z值最大,可以通过排序求得Z值(易知X向量是可能随L变化的)。注意Z是一个关于L的分段线性的凹函数。
一、二分法
  • 二分法的一般步骤就是首先给出所求值的大致范围,然后根据单调性逐渐缩小这个范围达到所需的精度。
  • 设最大的比值为L_max,即A*X <= (B*X)*L_max,当且仅当X为最优的选取时取得等号,Z(L_max) = 0。
  1. 若L > L_max,有A*X <= (B*X)*L_max < (B*X)*L,所以Z(L) < 0。
  2. 若L < L_max,当X为与L_max对应的最优选取时,易知Z(L) > 0。
二、Dinkelbach算法
  • 类似于牛顿迭代法求方程的解,设一方程f(x) = 0,在区间[a,b]内,f'(x),f''(x)保号。则我们设x[0] = a或者b,x[k+1]= x[k]-f(x[k])/f'(x[k]),左边的迭代方程就是求的过点(x[k],f(x[k]))切线与x轴的交点的横坐标,画个图就知道这个交点的x[k+1]处于曲线与x轴交点x*(方程的解)与x[k]之间。
  • 同样,我们也是求Z(L) = 0的值,虽然X可能随着L变化,但是易知这种变化是很缓慢的,我们可以近似地认为Z(L)在L[k]处的切线斜率为-B*X,则有L[k+1] = L[k]-Z(L)/(-B*X) =(A*X)/(B*X)。

二分法:
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define eps (1e-3)#define INF 0X7FFFFFFFclass solve{private:    int N,k;    int min_a,max_b;    int scores[2][1005];public:    solve(int x,int y):N(x),k(y)    {        processIn();        printf("%d\n",two_partition());    }    int processIn();    int two_partition();};int solve::two_partition(){    double left,right,mid;    double tmpDiff[1005];    double sum;    left = ((double)min_a)*100.0/((double)max_b);    right = 100;    while(right-left > eps)    {        sum = 0;        mid = (right+left)/2.0;        for(int i = 0;i < N;i++)        {            tmpDiff[i] = scores[0][i]*100.0-mid*scores[1][i];        }        sort(tmpDiff,tmpDiff+N);        for(int i = 0;i < N-k;i++)        {            sum += tmpDiff[N-1-i];        }        if(sum > 0)        {            left = mid;        }        else        {            right = mid;        }    }    return (int)(left+0.5);}int solve::processIn(){    min_a = INF;    max_b = -1;    for(int i = 0;i < N;i++)    {        scanf("%d",scores[0]+i);        min_a = min(min_a,scores[0][i]);    }    for(int i = 0;i < N;i++)    {        scanf("%d",scores[1]+i);        max_b = max(max_b,scores[1][i]);    }    return 0;}int main(){    int n,k;    while(~scanf("%d%d",&n,&k)&&n)    {        solve Dropping_tests(n,k);    }    return 0;}

Dinkelbach:

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>using namespace std;#define eps (1e-3)#define INF 0X7FFFFFFF#define maxN 1005struct node{    double unitL;    int index;    friend bool operator< (node x,node y)    {        return x.unitL > y.unitL;    }}L_array[maxN];class solve{private:    int N,k;    int scores[2][maxN];public:    solve(int x,int y):N(x),k(y)    {        processIn();        printf("%d\n",dinkelbach());    }    int processIn();    int dinkelbach();};int solve::dinkelbach(){    double pre_L,L;    double sum_a,sum_b;    pre_L = 0;    L = 50;    sum_a = 0.5;    sum_b = 1;    while(fabs(pre_L-L) > eps)    {        sum_a = sum_b = 0;        for(int i = 0;i < N;i++)        {            L_array[i].index = i;            L_array[i].unitL = (double)scores[0][i]*100.0-L*(double)scores[1][i];        }        sort(L_array,L_array+N);        for(int i = 0;i < N-k;i++)        {            sum_a += (double)scores[0][L_array[i].index]*100.0;            sum_b += (double)scores[1][L_array[i].index];        }        pre_L = L;        L = sum_a/sum_b;    }    return (int)(L+0.5);}int solve::processIn(){    for(int i = 0;i < N;i++)    {        scanf("%d",scores[0]+i);    }    for(int i = 0;i < N;i++)    {        scanf("%d",scores[1]+i);    }    return 0;}int main(){    int n,k;    while(~scanf("%d%d",&n,&k)&&n)    {        solve Dropping_tests(n,k);    }    return 0;}


0 0