01分数规划

01分数规划基本问题

给出两个有 n n n 个元素的数组 a i a_i ai b i b_i bi,对于一组 w i ∈ { 0 , 1 } w_i\in \{0,1\} wi{0,1},可能使得 ∑ i = 1 n a i w i ∑ i = 1 n b i w i \Large{\frac{\sum\limits_{i=1}^n a_iw_i}{\sum\limits_{i=1}^n b_iw_i}} i=1nbiwii=1naiwi 取得最值,求这个最值。

求解——二分法

check函数

我们二分答案。假设要求的是最大值。
二分得到一个答案 x x x,假设当前 x x x 小于最大值或者正好去到最大值,那么有 ∑ i = 1 n a i w i ∑ i = 1 n b i w i ⩾ x \frac{\sum\limits_{i=1}^n a_iw_i}{\sum\limits_{i=1}^n b_iw_i}\geqslant x i=1nbiwii=1naiwix,即 ∑ i = 1 n w i ( a i − x b i ) ⩾ 0 \sum\limits_{i=1}^n w_i\left(a_i-xb_i\right)\geqslant 0 i=1nwi(aixbi)0 。如果经过检验,不存在 ∑ i = 1 n w i ( a i − x b i ) ⩾ 0 \sum\limits_{i=1}^n w_i\left(a_i-xb_i\right)\geqslant 0 i=1nwi(aixbi)0 的情况,即 {%raw%} max ⁡ { ∑ i = 1 n w i ( a i − x b i ) } < 0 \max\left\{\sum\limits_{i=1}^n w_i\left(a_i-xb_i\right)\right\}<0 max{i=1nwi(aixbi)}<0{%endraw%},那么就与假设矛盾,根据反证法的思想,说明 x x x 已经超过最大值。

模板

#include<iostream>
#include<cstdio>
#define N 100003
#define eps 1e-6
using namespace std;
double a[N],b[N];
inline bool check(double x)
{
	int i;
	for(i=1;i<=n;i++)
	{
		/*若存在a[i]-x*b[i]非负
		那么若干非负的a[i]-x*b[i]之和
		必然非负
		即x必然为可行值
		*/
		if(a[i]-x*b[i]>=0)
		{
			return 1;//返回真
		}
	}
	return 0;
}
int main()
{
	cin>>n;
	int i;
	for(i=1;i<=n;i++) scanf("%lf",a+i);
	for(i=1;i<=n;i++) scanf("%lf",b+i);
	double l=0,r=1e9;
	double ans=0;
	//二分
	while(r-l>=eps)
	{
		double mid=(l+r)/2;
		if(check(mid)) 
		{
			ans=l;//mid可行,更新最终答案
			l=mid;
		}
		else r=mid;//mid不可行,答案比mid小
	}
	printf("%.6lf\n",ans);
	return 0;
}

例题

牛客竞赛 NC15446 wyh \text{wyh} wyh 的物品 ↬ \looparrowright

题目描述

wyh \text{wyh} wyh 学长现在手里有 n n n 个物品,这 n n n 个物品的重量和价值都告诉你,然后现在让你从中选取 k k k 个,问你在所有可能选取的方案中,最大的单位价值为多少(单位价值为选取的 k k k 个物品的总价值和总重量的比值)。

输入描述

输入第一行一个整数 T ( 1 ⩽ T ⩽ 10 ) T(1\leqslant T\leqslant 10) T(1T10)
接下来有 T T T 组测试数据,对于每组测试数据,第一行输入两个数 n n n k k k ( 1 ⩽ k ⩽ n ⩽ 100000 ) (1\leqslant k\leqslant n\leqslant 100000) (1kn100000)
接下来有 n n n 行,每行两个是 a a a b b b,代表这个物品的重量和价值。

输出描述

对于每组测试数据,输出对应答案,结果保留两位小数。

示例1

输入
1
3 2
2 2
5 3
2 1
输出
0.75

说明

对于样例来说,我们选择第一个物品和第三个物品,达到最优目的。

分析

设使得单位价值最大的 k k k 个物品的索引为 i 1 , i 2 , ⋯   , i k i_1,i_2,\cdots,i_k i1,i2,,ik。当二分得到一个答案 x x x ,假设 x x x 小于最大单位价值或恰好为最大单位价值,那么有 ∑ j = 1 k a i j b i j ⩾ x \sum\limits_{j=1}^k \frac{a_{i_j}}{b_{i_j}}\geqslant x j=1kbijaijx,即 ∑ j = 1 k ( a i j − x b i j ) ⩾ 0 \sum\limits_{j=1}^k(a_{i_j}-xb_{i_j})\geqslant 0 j=1k(aijxbij)0,若不存在 ∑ j = 1 k ( a i j − x b i j ) ⩾ 0 \sum\limits_{j=1}^k(a_{i_j}-xb_{i_j})\geqslant 0 j=1k(aijxbij)0 的方案,即 max ⁡ { ∑ j = 1 k ( a i j − x b i j ) } < 0 \max\left\{\sum\limits_{j=1}^k(a_{i_j}-xb_{i_j})\right\}< 0 max{j=1k(aijxbij)}<0,说明 x x x 大于最大值,右边界减小,否则 x x x 合法。

代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#define N 100003
#define eps 1e-6
using namespace std;
int value[N],weight[N];
double p[N];
int n,k;
bool check(double x)
{
	int i;
	for(i=1;i<=n;i++) p[i]=value[i]-x*weight[i];
	sort(p+1,p+1+n,greater<double>());
	double res=0;
	//前k大的value[i]-x*wight[i]之和即为最大值
	for(i=1;i<=k;i++) res+=p[i];
	return res>=0;//若非负则x合法
}
int main()
{
	int _;
	for(cin>>_;_;_--)
	{
		scanf("%d%d",&n,&k);
		int i;
		for(i=1;i<=n;i++) scanf("%d%d",weight+i,value+i);
		double l=0,r=1e9;
		double ans;
		//二分
		while(r-l>=eps)
		{
			double mid=(l+r)/2;
			if(check(mid))
			{
				ans=l;//mid合法,更新答案
				l=mid;
			}
			else r=mid;
		}
		printf("%.2lf\n",ans);
	}
	return 0;
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值