二分查找-POJ1064-Cable Master

题目大意

给定n条绳子的长度a[],如果从它们中切割出k条长度相同为ans的绳子,则ans最大能是多少(给定的绳子长和答案均保留两位小数)

1<=n,k<=1e5
1<=a[i]<=1e6

问题分析

  1. 问题的本质寻找可行解的上界,这与二分搜索的思想高度契合:在有序的一组解中利用二分不断缩小可能解的范围,直至答案在可接受的精度内,可以寻找可行解的上界和下界
  2. 当解为浮点数时,注意模板写法上与整数的区别
    1. 二分查找得到的总是满足判断条件的第一个解,即可行解的下界。为了得到可行解的上界,我们可以先求的不可行解的下界,这个不可行解下界的前一个,就是可行解的上界。因此我们可以在判断时做一下逻辑反转,使得求上下界的代码更加统一。严谨地看,这里隐含了一个前提:可行解与不可行解总是将值域分为两个连续的区域,即解的分布为(a)或(b),而不为©或(d)。事实上,很多问题都满足这样的分布,但在如上的逻辑反转操作中,最好还是确定一下分布条件是否成立。
      1. --------------++++++++
      2. +++++++++++++±-------
      3. --------++++++±------
      4. +++++++±------ +++++++
    2. 左边界l变化时,变化量应为eps而非1。
    3. 当获取结果时,注意与整数解的区别。对于整数解来说,上界解应该为ans=r-1。但对于浮点数解,由于无法做到直接按精度取整,所以需要一些处理技巧(后面讨论)

下面为浮点数二分搜索的代码模板:

//用于解有效性的判断
bool check(double x)
{
    //...
    if(x is OK)
        return true;
    else
        return false;
}

void solve()
{
    double INF=0x3f3f3f3f3f;
    double eps=1e-6;
    double l=0.0,r=INF;
    while(r-l>eps)//循环条件变松弛
    {
        double mid=l+(r-l)/2;
        //求下界时为check(),求上界时用!check()
        if(check(mid))
            r=mid;
        else
            l=mid+eps;//左边界移动时变化为eps
    }
    printf("%.2f",(floor)(r*100)/100);//保留两位小数的写法
}
  1. 以一个例子来说明浮点数解上界的确定办法。对于本题,2.00是可行解的上界,这意味着2.01就是一个不可行解。在处理时,eps并不直接是要求得精度1e-2,而是方便地选用了1e-6。这样,就导致了在寻找非可行解的过程中,得到的非可行解的精度更小,最后得到的非可行解的下界有可能是2.0042.006。因此,直接使用r-0.01然后舍去末尾得到的解上界是可能不对的,比如2.004-0.01=1.994 -> 1.99 。正确的做法应该是
printf("%.2f",(floor)(r*100)/100);

AC代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

const int maxn=1e5+10;

int n,k;
double a[maxn];

bool check(double x)
{
    int cnt=0;
    for(int i=0;i<n;i++)
        cnt+=(int)(a[i]/x);
    //printf("%.4f=%2d\n",x,cnt);
    return cnt>=k;
}

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++)
        scanf("%lf",&a[i]);
    double eps=1e-6;
    double l=0.0,r=0x3f3f3f3f;
    /*
    for(int i=0;i<100;i++)
    {
        printf("l=%.4f r=%.4f ==> ",l,r);
        double mid=l+(r-l)/2.0;
        //check:可以切出k段长度为mid的绳子
        if(!check(mid))
            r=mid;
        else
            l=mid+eps;
    }
    */

    while(r-l>eps)
    {
        //printf("l=%.4f r=%.4f ==> ",l,r);
        double mid=l+(r-l)/2.0;
        //check:可以切出k段长度为mid的绳子
        if(!check(mid))
            r=mid;
        else
            l=mid+eps;
    }

    printf("%.2f\n",floor(r*100)/100.0);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值