切绳子(含五种二分做法)

题目来自洛谷 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;
} 

猜你喜欢

转载自blog.csdn.net/weixin_61725823/article/details/125895858