题目来自洛谷 P1577 切绳子
题目描述
有 N 条绳子,它们的长度分别为 Li。如果从它们中切割出 K 条长度相同的绳子,这 K 条绳子每条最长能有多长?答案保留到小数点后 2 位(直接舍掉 2 位后的小数)。
输入格式
第一行两个整数 N 和 K,接下来 N 行,描述了每条绳子的长度 Li 。
输出格式
切割后每条绳子的最大长度。答案与标准答案误差不超过 0.01 或者相对误差不超过1%即可通过。
输入输出样例
输入 #1
4 11
8.02
7.43
4.57
5.39
输出 #1
2.00
说明/提示
对于 100% 的数据 0<Li <=100000.00,0<n<=10000,0<k<=10000
分析:
我们不知道切出的K段绳子每段有多长,但我们可以先假设切出的每段长度为x,然后看能切出多少段,与K做比较(将不易求的最值问题转化为容易的判定问题),就知道我们假设的x是偏大了还是偏小了,随后再次假设x,不断重复上面步骤直到刚好切出的长度为K。
【check函数里一般会让返回让x偏小或等于的情况为true,
即二分里相当于找第一个大于等于答案值X的数】
第一种浮点二分写法:
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int n,k;
double a[N];
char s[100];
bool check(double x)
{
int cnt=0;
for(int i=1;i<=n;i++)
cnt+=a[i]/x;
return cnt>=k;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%lf",&a[i]);
double l=0.00,r=100000.00;
while(r-l>1e-4)//一般题目要求精度多少,算的时侯就再至少提高两位精度
{
double mid=(l+r)/2;//注意浮点数的除法不再是向下取整
if(check(mid)) l=mid;//浮点二分不用注意边界问题,答案取l或r也均可
else r=mid;
}
sprintf(s+1,"%.3f",l);//因为题目要求直接舍掉2位后的小数,而C语言中保留小数会四舍五入,所以要先这样处理
s[strlen(s+1)]='\0';
printf("%s",s+1);
return 0;
}
第二种浮点二分写法:
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int n,k;
double a[N];
bool check(double x)
{
int cnt=0;
for(int i=1;i<=n;i++)
cnt+=a[i]/x;
return cnt>=k;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%lf",&a[i]);
double l=0.00,r=100000.00;
for(int i=0;i<100;i++)//无脑循环100次可以达到10^-30(2^-100)的精度,绝对够用
{
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.2f",floor(l*100)/100);//该操作相当于直接舍掉2位后的小数
return 0;
}
用浮点二分容易出问题(double精度不准),建议将浮点二分转化成整数二分来做。
第一种整数二分写法:
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
const int N=1e4+5;
int n,k;
int a[N];
double d[N];
bool check(int x)
{
int cnt=0;
for(int i=1;i<=n;i++)
cnt+=a[i]/x;//统计此时能分成几段
return cnt>=k;//分得多了,说明假设的x小了
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%lf",&d[i]);
a[i]=d[i]*100;//将输入的数据处理成整数方便后续二分答案
}
int l=0,r=1e7+1;//将边界l r设置为二分区间的左右外一层,如该题的二分区间为[1,10000000]
while(l+1!=r)//当l r相邻时退出循环
{
int mid=l+r>>1;
if(check(mid)) l=mid;//假设的x小了就往右半边找
else r=mid;
}
printf("%.2f",l/100.0);//当cnt==k时会更新l,所以答案取l而不是r
return 0;
}
第二种整数二分写法:
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int n,k;
int a[N];
double d[N];
bool check(int x)
{
int cnt=0;
for(int i=1;i<=n;i++)
cnt+=a[i]/x;
return cnt>=k;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%lf",&d[i]);
a[i]=d[i]*100;
}
int ans=-1;
int l=1,r=1e7;//l取可能取到的最小值,r取可能取到的最大值
while(l<=r)
{
int mid=l+r>>1;
if(check(mid)){
ans=mid;//更新答案
l=mid+1;
}
else r=mid-1;
}
printf("%.2f",ans/100.0);
return 0;
}
第三种整数二分写法:
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int n,k;
int a[N];
double d[N];
bool check(int x)
{
int cnt=0;
for(int i=1;i<=n;i++)
cnt+=a[i]/x;
return cnt>=k;//即x<=X(定义x为假设的切割长度,X为答案切割长度)
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%lf",&d[i]);
a[i]=d[i]*100;
}
int l=1,r=1e7;//l r取区间两端点值
while(l<r)
{
int mid=l+r+1>>1;//记得加上1,否则当l==r-1会永远mid=l l=mid死循环
if(check(mid)) l=mid;//x<=X即答案在右边,l更新为mid(因为mid也可能是答案)
else r=mid-1;
}
printf("%.2f",l/100.0);//答案取l或r均可,因为退出循环时l==r
return 0;
}