题目大意
给定n条绳子的长度a[],如果从它们中切割出k条长度相同为ans的绳子,则ans最大能是多少(给定的绳子长和答案均保留两位小数)
1<=n,k<=1e5
1<=a[i]<=1e6
问题分析
- 问题的本质寻找可行解的上界,这与二分搜索的思想高度契合:在有序的一组解中利用二分不断缩小可能解的范围,直至答案在可接受的精度内,可以寻找可行解的上界和下界。
- 当解为浮点数时,注意模板写法上与整数的区别
- 二分查找得到的总是满足判断条件的第一个解,即可行解的下界。为了得到可行解的上界,我们可以先求的不可行解的下界,这个不可行解下界的前一个,就是可行解的上界。因此我们可以在判断时做一下逻辑反转,使得求上下界的代码更加统一。严谨地看,这里隐含了一个前提:可行解与不可行解总是将值域分为两个连续的区域,即解的分布为(a)或(b),而不为©或(d)。事实上,很多问题都满足这样的分布,但在如上的逻辑反转操作中,最好还是确定一下分布条件是否成立。
- --------------++++++++
- +++++++++++++±-------
- --------++++++±------
- +++++++±------ +++++++
- 左边界l变化时,变化量应为eps而非1。
- 当获取结果时,注意与整数解的区别。对于整数解来说,上界解应该为ans=r-1。但对于浮点数解,由于无法做到直接按精度取整,所以需要一些处理技巧(后面讨论)
- 二分查找得到的总是满足判断条件的第一个解,即可行解的下界。为了得到可行解的上界,我们可以先求的不可行解的下界,这个不可行解下界的前一个,就是可行解的上界。因此我们可以在判断时做一下逻辑反转,使得求上下界的代码更加统一。严谨地看,这里隐含了一个前提:可行解与不可行解总是将值域分为两个连续的区域,即解的分布为(a)或(b),而不为©或(d)。事实上,很多问题都满足这样的分布,但在如上的逻辑反转操作中,最好还是确定一下分布条件是否成立。
下面为浮点数二分搜索的代码模板:
//用于解有效性的判断
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);//保留两位小数的写法
}
- 以一个例子来说明浮点数解上界的确定办法。对于本题,2.00是可行解的上界,这意味着2.01就是一个不可行解。在处理时,eps并不直接是要求得精度1e-2,而是方便地选用了1e-6。这样,就导致了在寻找非可行解的过程中,得到的非可行解的精度更小,最后得到的非可行解的下界有可能是2.004、2.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;
}