01分数规划 可以看懂的博客

              【二分搜索/Dinkelbach算法】

问题描述:有N个物体,它们的利益用v[i]表示,代价用c[i]表示。现在要在这N个物体中选取K个物体,使得选出来的这K个物体的总利益除以总代价达到最大值。即取得最大值。

问题转化:构造一个x[N]的数组,表示每个数取或不取的状态,显然每一个x[i]只有两个取值:01,其中1表示取,0表示不取。则整个式子也就可以变成目标式:

值得注意的是:上式中的r是每一组x对应求得的当前答案,而我们的目的就是要找到一组x使得求出来的r得到最大值R!(这很重要!)

 一、二分法

进一步对式子进行处理:

简单的一项处理:

构造一个函数:

其中F(r)在平面坐标系上体现为一条直线,每一组x[i]都分别唯一地对应一条直线,这些直线的截距均大于等于0、斜率均小于等于0。而这些直线在X轴上的截距就是这一组x求出来的r,而截距的最大值就是我们要求的R。(如下图所示)

 

X轴上面任取一个r,如果至少有一条直线的F(r)>0,那么说明了什么呢?

扫描二维码关注公众号,回复: 892369 查看本文章

说明至少还有一条直线与X轴的交点在它的右边,那么这个r一定不是最大值,真正的最大值在它的右边。反之,如果所有的F(r)都小于0,那么真正的最大值在它的左边

 

那么前面的结论就可以换种说法,因为我只需判断最大的那个F(r)的正负性就行了:

随便取一个r,如果F(r)max>0,则结果R>r,反之若F(r)max<0,则结果R<r。直到找到使F(r)max=0r,那个r就是我们要找的结果R

显然这是一个令人感到兴奋的结论,因为我们自然而然就想到了一种方法:二分法!

至此,还有一个小小的问题没有解决,那就是F(r)max怎么求?

 

二分代码;

#include<cstdio>

#include<cstring>

#include<string>

#include<algorithm>

#include<cmath>

using namespace std;

const int maxn = 2005;

const int INF = 0x3f3f3f3f;

int n, m;

double a[maxn];

double b[maxn];

double t[maxn];

double judge(double k)

{

for (int i = 0; i < n; i++)

t[i] = a[i] - k * b[i];

sort(t, t + n);

double ans = 0;

for (int i = m; i < n; i++)

ans += t[i];

return ans;

}

int main()

{

while (scanf_s("%d%d", &n, &m) != EOF)

{

if (n + m == 0)

break;

for (int i = 0; i < n; i++)

scanf_s("%lf", &a[i]);

for (int i = 0; i < n; i++)

scanf_s("%lf", &b[i]);

double l = 0.0;

double r = 1.0;

while (r - l > 1e-8)

{

double mid = (r + l) / 2;

if (judge(mid) > 0)

{

l = mid;

}

else

r = mid;

}

printf("%1.f\n",l * 100);

}

return 0;

}

二分是一个非常通用的办法,但是我们来考虑这样的一个问题,二分的时候我们只是用到了F(r)>0这个条件,而对于使得F(r)>0的这组解所求到的R值没有使用。因为F(r)>0,我们已经知道了R是一个更优的解,与其漫无目的的二分,为什么不将解移动到R上去呢?

 

我们再次回到函数表达图:

 

这个是二分的原理表现,在图中,我们取左界为0,右界为足够大,画出中间的(+)/2,发现此时F(r)max是大于0的,那么继续将左界移向(+)/2,继续二分,直到找到R为止。

但是我们换种思路,我们在判断一个当前的r的时候需要去求一个F(r)max,在二分之中我们仅仅判断了F(r)max0的关系,这是利用率比较低的。其实我们可以将F(r)max利用起来。找到F(r)max所在的那一条直线,然后将r移动到这条直线的截距上面去(如下图,找到当前的F(r)max所在的直线,将r移动到r4上面去,这样做甚至只要2步即可到位)

 

 

二、Dinkelbach算法

 

它实质上是一种迭代,他是基于这样的一个思想:他并不会去二分答案,而是先随便给定一个答案,然后根据更优的解不断移动答案,逼近最优解。由于他对每次判定使用的更加充分,所以它比二分会快上很多。

在这个算法中,我们可以将r初始化为任意值,不过由于所有直线都只有y轴右边的部分,所以一般将r初始化为0

代码

struct node

{

double a, b, d;

}q[maxn];

int cmp(struct node x, struct node y)

{

return x.d<y.d;

}

double Dinkelbach()

{

double L ;

double ans = 0;

while (1)

{

L = ans;

double x = 0;

double y = 0;

for (int i = 0; i < n; i++)

q[i].d = q[i].a - L * q[i].b;

sort(q, q + n,cmp);

for (int i = k; i < n; i++)

{

x += q[i].a;

y += q[i].b;

}

ans = x / y;

if (fabs(ans - L) < 1e-7)

return L;

}

 

}

int main()

{

while (scanf_s("%d%d", &n, &k) != EOF)

{

if (n + k == 0)

break;

for (int i = 0; i < n; i++)

scanf_s("%lf", &q[i].a);

for (int i = 0; i < n; i++)

scanf_s("%lf", &q[i].b);

printf("%.0f\n", Dinkelbach() * 100);

}

return 0;

}

 

猜你喜欢

转载自blog.csdn.net/yihanyifan/article/details/80316240