二分的姿势的选取
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次的循环则可以达到 2−100≈10−30 的精度范围。或者,也可以根据需要的精度算出需要二分的次数。
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就是递减的。
例题
以上是关于二分的姿势的选取的主要内容,如果未能解决你的问题,请参考以下文章