二分的姿势的选取

Posted SuPhoebe

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分的姿势的选取相关的知识,希望对你有一定的参考价值。

解的范围为实数

精度判断

这样做是最基础的方法,但是不是很推荐。会存在浮点误差。

left = 0.0, right = 0x3f3f3f3f;    
while (dcmp(right - left) != 0)    
      mid = (right + left) / 2.0;    
      if (judge(mid)) right = mid;    
      else left = mid;    
  

次数判断

比较推荐这种写法。

直接指定循环次数(100次)作为终止条件。1次循环可以把区间的范围缩小一半,100次的循环则可以达到 21001030 的精度范围。或者,也可以根据需要的精度算出需要二分的次数。

cnt = 100
left = 0.0, right = 0x3f3f3f3f;    
while (cnt --)    
      mid = (right + left) / 2.0;    
      if (judge(mid)) right = mid;    
      else left = mid;    
  

解的范围是整数

知乎上用排列组合做了一波64种姿势,回答地址

不说他分析的正确性。这儿就讲四种。

最大化最小值

此种题目一般是“二分值越小越容易满足条件”,然后求符合条件的最大值。比如,lower_bound函数。

区间长度为1时的写法:
左闭右闭形式,解的范围为 [lb,ub]

// 计算区间为[lb, ub]
while( lb < ub )  // 区间长度为1时终止循环

    int mid = lb + (ub - lb + 1) / 2;    // 由于是区间长度为1时终止循环,所以要加1
    if( ok(m) ) lb = mid;
    else ub = mid - 1;

// 
跳出循环时 lb == rb

区间长度为2时的写法:
左闭右开形式,解的范围为 [lb,ub)

int lb = 1, ub = N+1;   // 注意,从初始值ub就是取开区间了 
while(lb + 1< ub)      // 区间长度为2时终止循环
    int mid = (ub + lb) / 2;  
    if(judge(mid)) lb = mid;  
    else ub = mid;  
  
// 跳出循环时 lb + 1 == ub
// 答案为 lb

最小化最大值

此种题目一般是“二分值越大越容易满足条件”,然后求符合条件的最小值。如,upper_bound函数。

区间长度为1时的写法:
左闭右闭形式,解的范围为 [lb,ub]

while( lb < ub )

    int m = lb + (ub - lb) / 2;     // 这里虽然区间长度为1,但不需要加1(不会死循环)
    if( ok(m) ) ub = m;
    else lb = m + 1;

// 跳出循环时 lb == ub

区间长度为2时的写法:
左开右闭形式,解的范围为 (lb,ub]

int lb = 0, ub = N;  //注意,从初始值开始,左侧就取的开区间
while(lb + 1< ub)  
    int mid = (ub + lb) / 2;  
    if(judge(mid)) ub = mid;  
    else lb = mid;  
  
// 跳出循环时 lb + 1 == ub
// 答案为 ub

但是在所有情况下,上面的几种形式都是等价的呢? 答案是不是的。

结论:上述形式的选取和judge对应的函数的单调性有关。如果judge函数是单调递增的,应该选取左开右闭形式。反之,如果judge函数是单调递减的,应该选取左闭右开的形式。

关于judge函数的单调性,比如,对于一个递增的数组,下标位置越大的数,值越大,越不可能是最终的答案。所以这个时候judge就是递减的。

例题

Leetcode中的二分

以上是关于二分的姿势的选取的主要内容,如果未能解决你的问题,请参考以下文章

POJ 3273 Monthly Expense (二分&最大化最小值)

二分与三分

一元三次方程求解 实数二分

二分查找法

分治算法-----二分求最大最小

BZOJ3130: [Sdoi2013]费用流[最大流 实数二分]