原题: http://acm.nyist.net/JudgeOnline/problem.php?pid=914
//nyoj 914 二分+巧妙的贪心
//思路:因为题目限定选k个物品值是固定的,而结果rs的范围我们也是可以知道的,即0<rs<=m(m为单位价值最大的的某个物品),所以采用二分法,每一轮判断中间值mid的合理性来缩小范围
// 如何判定mid的合法性? 这是这题的难点,也是最巧妙的一点
// 假设单价为mid, W为所选的 k个物品的总质量, w1~wk为各个物品的重量,V1~Vk为各物品的总价值
// 则数学计算得到公式 mid <= (V1+V2+...Vk)/W 满足即为合法,公式等价转化为
// ----> mid * W <= V1+V2+...Vk
// ----> mid *(w1 +w2+....+wk) <= V1+V2+...+Vk
// ----> (V1+V2+...+Vk) - (mid*w1 + mid *w2+...+mid*wk) >=0
// ----> (V1-mid*w1) + (V2-mid*w2) +... +(Vk-mid*wk) >=0 最终式子①
// 所以说问题转化为 对于给定的mid,如果能找到k个物品,如果他们满足上面这个 式①这个mid就是合理的。
// 这时候就可以贪心了,用一个数组arr存放每个物品 mid*wi-Vi的结果,然后从大到小排序,看前k个相加能否>=0能即为合理,返回1,否则则返回0
//小细节:这里为了方便处理精度问题,我把结果进行了乘100处理,如果不理解可以忽略我处理精度的地方。
//这题之前做过,但是WA了很多次,没有做出来。也是看了别人的思路才做出来的。现在自己做总结,希望加深印象,对二分和贪心有更深刻的认识。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct M
{
double wi;
double vi;
}m[10001];
int n,k; // n个物品,选k个
double max(double a,double b)
{
if(a>=b)return a;
return b;
}
int cmp(const void * a,const void * b) //快排,从大到小排序
{
return *(double *)a < *(double *)b;
}
int greed(double per) //检测per的合法性
{
per=per/100;
double arr[10001];
for(int j=0;j<n;j++)
{
arr[j]=m[j].vi- per*m[j].wi; //记录 Vi-mid*wi
}
qsort(arr,n,sizeof(arr[0]),cmp);//对 Vi-mid*wi 的结果排序
double sum=0;
for(int j=0;j<k;j++) //前k个相加
{
sum=sum+arr[j];
}
if(sum<0)return 0;//小于0,不满足式①,不合法,返回0
return 1;//大于等于,返回1
}
int main()
{
while(~scanf("%d %d",&n,&k))
{
int i;//临时变量
double r=-1; //区间右边界
for(i=0;i<n;i++)
{
scanf("%lf %lf",&m[i].wi,&m[i].vi);
r=max(r,m[i].vi/m[i].wi); //找到单价最高的那个物品
}
double l=0; //区间左边界
r=r*100; //精度处理
double rs=0; //存放结果
while(l<=r)
{
double mid=(l+r)/2;
if(greed(mid)){ //如果合理
rs=mid; //暂存mid值
l=mid+0.01; //并增大l值
}else{ //如果不合理
r=mid-0.01; //减小r值
}
}
printf("%.2lf\n",rs/100);
}
return 0;
}