二分答案之切碎那个切绳子

切碎那个切绳子
目录
1.二分答案与二分查找的不同
2.切绳子的细节解释
切绳子这题耗了我一两天,发现是二分查找的思想禁锢了我,查找了很多资料,了解了二分答案后,发现天空一片晴朗,写题还是过不了…
因为,细节注意不到位,多少心思都白费。
所以,这里我着重说一下二分答案的简单思想,以及对于切绳子代码最彻底的解释。
1.二分答案与二分查找的不同
首先,二分查找是用来查找一个升序或降序的数组的某个元素,这里不重点讲,上一道我们老师出的题及其代码。

从键盘输入一个整数,用折半查找法找出该数在10个有序整型数组a中的位置。若该数不在a中,则打印出相应信息。试编程。

#include<stdio.h>

int main()

{

    int a[10]= {1,3,5,7,9,11,13,15,17,19},top,bot,mid,m;

    printf("Enter a integer:");

    scanf("%d",&m);

    top=0;

    bot=9;       

    while(top<=bot)

    {

        mid=(top+bot)/2;

        if(a[mid]==m) break;

        else if(a[mid]<m) top=mid+1;

        else bot=mid-1;

    }

    if(top<=bot) printf("Found! position is %d.\n",mid);

    else printf("Not found %d.\n",m);

    return 0;

}

其实根据我对英文的理解,bot应该表示最小序号,top表示最大序号,不过没事,不影响

那么重点来了,什么时候用二分答案呢
根据我对大佬们文章的拜读,大概两种情况
1.寻求最大或最小值
2.不好直接求答案,但你随便猜一个数m,你能很快判断m可不可能是答案。
再进一步解释,题目要得到的答案,一般都是最值,所以 题目要的答案需要满足两个条件
1.它满足题目的要求
2.它是最优解
这么讲太浮空了,上题
有 N条绳子,它们的长度分别为 Li。如果从它们中切割出 K 条长度相同的绳子,这 K 条绳子每条最长能有多长?答案保留到小数点后 2位(直接舍掉 2位后的小数)。
输入输出样例
输入

4 11
8.02
7.43
4.57
5.39
输出
2.00

忘掉你刚刚看到的数据,我现在问你:
你家装修剩余了4根细麻绳,现在你妈想把它们改造成11根长度相同的抽人的鞭子,当然尽量别浪费材料,所以这11根绳子越长越好,这4根麻绳的长度分别是8.02,7.43,4.57,5.39 那么,请问你最长能把鞭子切成几米?

现在你妈笑眯眯地对你说:宝贝儿,切几米?得要精确到小数点后两位哦 ~

你就说,是让人干的事吗?!!

这时你妈又问你:切1米行吗?

你拿1去算,发现可以切8+7+4+5根绳子,可是可以切11根绳子出来,但剩下的13根绳子也太浪费了,1显然不是最优解。
怎么样?你是不是下意识就很快判断出1这个答案到底行不行?
所以像这种很方便套答案的题目,就很可能用到二分答案(答案的范围我们是知道个大概的)
而二分答案,自然是神在 二分 这两个字
还是以上面这组数据为例
我们把答案范围设为[0,20]
(0+20)/2==10
你一估摸,肯定不行呀,所以[10,20]这一段的答案就可以舍弃不要了对吧,一下就把范围减少了一半。爽不爽?

然后你又进行了同样的几波操作,发现,1.25米是可以切出11根出来的,那[0,1.25]这个区间不都是可行区间吗?切出来的绳子只会>=11根,最优解只会>=1.25,所以[0,1.25]这个区间又可以不管了。

就是如此这般就可以把答案范围缩小,区间左端点离右端点越来越近,最后在误差允许的范围内就可以把答案输出了。

而二分答案与二分查找区别之一:二分答案是要找一个边界,也就是区间的一个端点,所以后面切绳子的代码最后输出的是,区间右端点r,而不是mid。而二分查找就是用mid去找那个元素,它一定能落实到元素那个个体,而不是个估计值。

再把切绳子的题目完整上一遍

*有 N 条绳子,它们的长度分别为 Li。如果从它们中切割出 K条长度相同的绳子,这 K条绳子每条最长能有多长?答案保留到小数点后 2位(直接舍掉 2位后的小数)。
输入格式 第一行两个整数 N 和 K,接下来 N行,描述了每条绳子的长度 Li。
输出格式 切割后每条绳子的最大长度。答案与标准答案误差不超过 0.01 或者相对误差不超过 1%即可通过。
输入输出样例
输入
4 11
8.02
7.43
4.57
5.39
输出
2.00
说明/提示 对于100% 的数据 0<Li≤100000.00,0<n≤10000,0<k≤100000

二分答案的思想 贫道已经说了,那么下面上代码,并将对代码进行细节解释:

#include<stdio.h>
double a[10100];
int main() {
 int n,k,i,sum=0,mid;
 scanf("%d %d",&n,&k);
 for(i=0; i<n; i++) {
  scanf("%lf",&a[i]);  //输入每根绳子的长度
  a[i]*=100;    //将每根绳长扩大100倍存起来
 }
 double l=0,r=20000100;  //答案范围左右端点,l表示left,r表示right
 while(l<=r) {   //把答案二分的循环
  sum=0;
  mid=(l+r)/2;
  if(mid==0)break;   //特判,加强代码的健壮性
  for(i=0; i<n; i++)
   sum+=a[i]/mid;   //计算能切几根绳子出来
  if(sum>=k)
   l=mid+1;
  else
   r=mid-1;
 }
 printf("%.2f",r/100.0);  //输出答案
}

那么问题来了


#include<stdio.h>
double a[10100];
int main() {
 int n,k,i,sum=0,mid;   //问题一:为什么mid要是整型?
 scanf("%d %d",&n,&k);
 for(i=0; i<n; i++) {
  scanf("%lf",&a[i]);
  a[i]*=100;    //问题二:为什么a[i],也就是每根绳子的长度要乘以100?
 }
 double l=0,r=10000100;  //问题三:题目中说Li<=100000,也就是已知绳子
                  //的长度<=100000,那为什么答案范围右端点 r要开这么大
 while(l<=r) {
  sum=0;
  mid=(l+r)/2;
  if(mid==0)break;   //这是个特判,加强你代码的健壮性
  for(i=0; i<n; i++)
   sum+=a[i]/mid;
  if(sum>=k)
   l=mid+1;    //mid+-1这里其实还有学问,只是不知道等下我能不能讲清楚
  else
   r=mid-1;
 }
 printf("%.2f",r/100.0);  //问题四:为什么输出的是r/100,而不是mid/100?
}

请详细看代码块中贫道提出的问题
这是新手很容易搞不清楚的地方,因此明明代码和通过的人非常相似,却始终无法通过的原因

开始解释:

问题一:想要解决问题一,先解决问题二

问题二:为什么要把每根绳子长度扩大100倍,首先注意到题目的要求了没有?保留小数点后两位(直接舍掉2位后的小数)

这个要求其实不简单,直接舍掉,它不能四舍五入。所以用到了常用的技法,先乘以100,后来又转换为 int型数据再除以100。

而 mid为什么要是整型也就是因为这个原因,绳子需要保留的两位小数已经通过乘以100保住了,需要的数据就移到小数点前面了。

所以你拿整型去除它就可以。再者我们最后要输出的r它只需要小数点前面的数据,显然 r=mid-1,r要是整型,mid=r+1的mid自然也是整型。

好,那么开始问题三:
为什么 r要开到10000100如此之大
这是因为题中要求绳长<=100000;而刚刚我们为了保住那两位小数,把所有绳长都扩大了 100倍,所以相应的答案范围右端点也要扩大 100倍,这是个细节,但是没注意就过不了,别问我怎么知道的,错出来的。

最后一个问题:为什么利用 r来输出,而不利用 mid?
因为 r保存的才是那个边界,那个最大值。解释如下:
二分循环while的条件是 l<=r,而 l=mid+1, r=mid-1;当r-l<1时,再来一个循环 l就大于 r,因此循环结束。

这里mid加减的1也有一个小学问,因为所有绳长元素都扩大了100倍,所以这里加减1,相当于原来元素加减0.01,之前我还老担心 l经过mid加个一,会把正确答案排在外面,想通之后就明白不会了,在误差允许范围内。

假设允许误差为t
就是答案区间中的一个点是正确值,然后当l=r时,或者r-l<允许误差t时r就是正确答案,而此时还要进行最后一次循环,l加上了t,也就是 次l=l+t,而l+t>r,所以此时的l>r,循环结束。
r保留了可行的最大值。所以输出r

这里应该有两种情况,一种是最后一次mid可行,另一种是最后一次mid不可行,不管哪种,right都是最后那个保留了的边界。

唉,不知道我讲清楚了没有,大家可以画个线段图手动模拟下。

好啦,到这里问题就都解决啦,再说一句我个人的体悟:通常二分查找用mid找答案,二分答案用right找答案,而方便套答案来判断这个答案可不可能是正确答案,且与求最值有关的题,就适合用二分答案
终于写完啦,要感谢和我一起讨论问题的学习伙伴,然后如果文章哪里有错误,异常欢迎大家告诉我。我一定认真更正。好啦,微臣先告退啦~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值