Ⅰ.二分算法:
二分算法是非常精妙的算法,可以将许许多多问题转化为判定的问题。二分的应用范围十分广泛,并不仅仅应用在单调区间上查找数值。而且二分算法经常与离散化配套使用,是居家旅行必备的算法。什么时候可以对问题进行二分?二分的本质并不是单调性,而是我们通过某种判定,使得区间的前半部全部满足而后半部分全部不满足,此时就可以二分了。
二分算法虽然有模板且代码量短,但是许多细节需要注意,非常容易陷入死循环或者数组越界的麻烦(整数域的二分)。我们最好深刻理解,我们通过二分最后得出的位置究竟是代表什么意义。(本人也得好好地累积经验深刻理解,还是太菜了)
Ⅱ.整数域的二分算法模板:
一、模板1
代码:
模板1:
//l, r 初始化成取值范围的左右边界
while( l < r ){
int mid = l + r >> 1;
if( check(mid) ) r = mid;
else l = mid + 1;
}
//二分结束后,l==r,l,r都是最终的答案
个人理解:(图 + 文)
如上图(模板1)。x代表的是满足判定的第一个点的位置。
①若 mid >= x ,则说明答案必在mid的左边(包含mid),也就是答案会落在 [ l , mid ] 区间内。此时更新:r = mid 即可。
②否则 mid < x ,则说明答案严格在mid的右边,答案会落在 [ mid + 1, r ] 区间内。此时更新: l = mid + 1 即可。
二、模板2
代码:
while( l < r ){
int mid = l + r + 1 >> 1;
if( check(mid) ) l = mid;
else r = mid - 1;
}
个人理解:
如上图(模板2):x代表满足判定的最后一个点的位置。
①若 mid <= x ,则说明答案必在mid的右边(包含mid),也就是答案会落在 [ mid , r ] 区间内。此时更新:l = mid 即可。
②否则 mid > x ,则说明答案严格在mid的左边(不包含mid),答案会落在 [ l, mid - 1 ] 区间内。此时更新: r = mid - 1 即可。
三、总结:
相信你已经发现,模板一和模板二的差异主要集中在 l 和 r 的更新方式上,更新方式的判断如上文所述就可以了。还有 mid 的取值的不同,mid 的取值是有规律的,这里直接摆出规律作为参考:
当更新方式为 r = mid 的时候, mid 的取值是 mid = l + r >> 1 。
当更新方式为 l = mid 的时候, mid 的取值是 mid = l + r + 1 >> 1 。
当然了,二分算法的写法各式各样。适合自己的,并且自己理解不会出错的才是最好的。
Ⅲ.实数域上的二分
实数域上的二分就比整数域的二分简单多了。因为不涉及左右区间的取舍或者边界问题。注意所需要的精度即可。这里直接甩模板:
double esp = 1e-6;//esp是精度,精度越高越逼近答案。
while( l + esp < r ){
double mid = ( l + r )/2;
if( cal(mid) ) r = mid;
else l = mid;
}