之前二分一直用lower_bound,遇到麻烦的题不方便。仔细研究二分真的有很多细节的点。
- 二分模板一共有两个,主要看mid在期望答案的右边还是左边
- 二分过程全部使用右移运算
>>1
而不是/2
。因为右移运算是向下取整,而整数除法是向零取整,在二分值域包含负数时后者不能正常工作
整数域上二分
1. mid在期望答案的右边(mid大)
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
2. mid在期望答案的左边(mid小)
- 缩小范围时,
l=mid
,r=mid-1
,中间值mid=(l+r+1)>>1
,如果中间值取mid=(l+r)>>1
,则当r-l=1
时,有mid=(l+r)>>1=l
,接下来若进入l=mid
分支死循环;若进入r=mid-1
分支,造成l>r,循环不能以l=r
结束。
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
3. 处理无解情况:
因为mid = (l+r)>>1不会取到r这个值,mid = (l+r+1)>>1不会取到l这个值
把[1,n]扩大为[1,n+1]和[0.n],把a数组的一个越界下标包含进来,如果最后二分终止于扩大后的这个越界下标上,则a中不存在所求的数。
实数域上二分
一般保留k位小数时,取eps=10−(k+2)eps=10^{-(k+2)}eps=10−(k+2)
while (l+1e-5 < r)
{
double mid = (l + r) / 2;
if (calc(mid)) r = mid;
else l = mid;
}
或者直接使用固定次数的二分,精度会更高,但容易超时
for (int i = 0; i < 100; i ++)
{
double mid = (l + r) / 2;
if (calc(mid)) r = mid;
else l = mid;
}