算法习题--电缆分割问题(二分法)

  刚加了新群,每天一道算法题,希望能坚持下来。算法小白做题真的扎心,一直在百度。。。

  某地区即将举行区域程序设计比赛,竞赛委员会已经成立并决定举行一次最公平的竞赛, 他们决定利用星形拓扑结构来连接每个竞赛者的电脑---也即连接这些电脑到一个中心HUB上。为了达到真正的公平竞赛目的,竞赛委员会主任下令要求:每个竞赛电脑连接到中心HUB的电缆必须是一样长的。竞赛委员会联系了一个本地的电缆老板,要求老板为他们提供一定量的相同长度的电缆,而且要求电缆长度越长越好。通过调查,电缆老板知道仓库中每根电缆的长度(精确到厘米),而且他可以以厘米的精度剪断电缆,但确不知道他能为竞赛委员会提供的每根电缆的最大长度是多少? 
你的任务就是:编程求出每根电缆的最大可能的长度。

  输入:
  第1行,2个整数N和K,N是仓库中的电缆条数,K是竞赛委员会要求的电缆条数。 
  其中 1 < N < 10000, 1 < K < 10000 
  第2至第N+1行,每行为仓库中的一条电缆的长度,单位为米。

  输出:
  仅1行,为提供给竞赛委员的电缆最大长度(精确到小数点两位)

  

  这道题是放在vj上的,全英文看到吐,用翻译翻过来之后,觉得完全没有思路。虽然已经提示了今天的题是关于二分法的还是没有想法。对于二分法,我的理解就是两个数(left,right)取中间值mid 比较中间值和要求的值a 得出继续在(left,mid) || (mid,right)继续重复该过程,知道=a(或者说无限接近a)。

  然后下面就是群里大佬们的讲解了:

1.二分查找基于数列的单调性,也就是要求数列有序,从中可以看出单调性是最重要的性质
2.那么这道题单调性在哪里呢?
切割电缆的长度x和能切出来的段数lx,总是满足长度越短能切下来的段数越多,所以如果x能切下来的段数(lx)如果大于k,那么只要考虑(x,r]这一段就可以了
所以我们对于切割电缆的长度这个关键字二分,对于所有mid,判断是否能切出k段。每次判断扫一遍数组中所有元素O(n),二分乘上O(logn) 于是最终复杂度O(nlogn)
3.二分好处是什么?
就好比我已知答案去验证一般来说比直接求出答案简单,那么二分答案的功能就是将难求的答案转化为易验证的答案。
4.如何看出能使用二分答案?关键在于单调性能否看出
这道题是典型的满足单调性,但有的题目隐藏比较深,不过总的来说,看出单调性都不是最难的一步,后面进行验证的过程是很复杂的,有可能跟很多算法、数据结构挂钩。

  分析此题:1.先求得最大划分的可能长度m(所有电缆总长度/要求划分的根数k)  然后在0到m之间查找即可.

       2.由于是小数,所以要用二分查找,每次找(left+right)/2的长度看这样划分是否符合要求的根数,

       如果符合再看是否是最大的可能(即right和left足够接近),不符合继续递归即可。

       3.要注意的是 小数的判断不能用a==b 这样的形式,而要用b-a<C 这样的形式

      (C可以是足够小的小数,如b-a<0.01)来判断两个数是否接近。

      (更多关于浮点数注意事项前面有一篇文章专门写的)

 1 import java.text.DecimalFormat;
 2 import java.util.Scanner;
 3 
 4 public class Day1二分法 {
 5     static Scanner sc=new Scanner(System.in);
 6     static int n=sc.nextInt();
 7     static int k=sc.nextInt();
 8     static double []l=new double[n];
 9     static double p;//满足要求的最大电缆长度
10     
11     public static void search(double left,double right){//二分搜索
12         if(right<0.01)p=0.0;//小于1cm不可分
13         else{
14             double mid=(left+right)/2.0;
15             int num=0;//num是->用mid来切分得出的电缆根数
16             for(int i=0;i<l.length;i++){
17                 num+=(int)(l[i]/mid);
18             }
19             if(right-left<0.01&&num==k)p=mid; //找到,结束
20             else if(num==k)search(mid,right);////数量符合,但是电缆长度不是最大,说明划分小了,在mid和right中再找。  
21             else if(num>k)search(mid,right);//电缆数太多,说明划分太小,需要在mid和right中再找。
22             else search(left,mid);//电缆数少,说明划分大了,需要在left和mid中再找。
23         }
24     }
25     public static void main(String[] args) {
26         for(int i=0;i<l.length;i++){
27             l[i]=sc.nextDouble();    
28         }
29         double length=0.0;//预定义一个长度变量,用于计算现有电缆总长度
30         for(int i=0;i<l.length;i++)length+=l[i];//通过循环得出了现有电缆总长度
31         search(0.0,length/k);//length/k为最大划分的长度
32 p=(double)(int)(p*100)/100; 33 DecimalFormat df=new DecimalFormat("###0.00"); 34 System.out.println(df.format(p));//向下取整 35 } 36 }

 最后,有一个精度问题。保留两位小数,其实如果单纯保留两位小数不算难,但这个应该是非四舍五入的,而是多的都不要。eg:2.1293->2.12

因为这个精度问题我一直都没AK,又问了大佬,他们说的是用 p=(double)(int)(p*100)/100; 就过了。但是,我还是没过哎,我让他们帮忙看了代码,没有什么问题,测了几组样例也都一样,不知道为什么。毒瘤题,同样的做法 就是有人过了有人没过。我也试过了如果四舍五入的也不过。算了算了,方法会了就好,至于这个精度问题继续留坑,等以后的我回来补吧。

ps:前几天看的时候 忽然发现,java有自带的二分法哎。真是扎心,我这个是跟着大佬写的c改的。


原文地址:https://www.cnblogs.com/ShallByeBye/p/8421704.html