定义:二分的基础用法是在单调序列或者单调函数中进行查找。
根据复杂度理论,我们可以通过将求解改为判定的方法,优化算法。这是一种非常基础,又比较容易写错的算法。本文将阐述一种常见的二分方法。
整数集合上的二分
保证最终答案处于闭区间 $ [l,r] $ 以内,循环以 $ l == r $ 结束,每次二分的中间值会归属于左半段或者右半段。
应用:
单调递增序列求 $ x $ 或 $ x $ 的后继:
while (l < r) {
mid = (l + r) / 2;
if (a[mid] >= x) r = mid;
else l = mid + 1;
}
单调递增序列求 $ x $ 或 $ x $ 的前驱:
while (l < r) {
mid = (l + r + 1) / 2;
if (a[mid] <= x) l = mid;
else r = mid - 1;
}
注意:可行解一定要在选取的区间之内,一定不能造成没有缩小可行区域的情况
如上面两种代码所示,这种闭区间的二分有两种形式:
- 缩小范围时,$ r = mid $, (l = mid + 1),取中间值时,(mid = (l + r) / 2)
- 缩小范围时,$ l = mid $, (r = mid - 1),取中间值时,(mid = (l + r + 1) / 2)
思考一下,如果第二种情况仍然使用 (mid = (l + r) / 2), 如果 (r - l == 1) 那么 (mid) 的值就会和 (l) 一样,会造成没有缩小可行区域的情况,进入死循环
仔细分析这两种 (mid) 的取值,我们还发现:(mid = (l + r) / 2) 不会取到 (r) 这个值,(mid = (l + r + 1) / 2) 不会取到 (l) 这个值。我们可以利用这一性质来处理无解的情况,即:把最初的二分区间 ([1,n]) 变成 ([0,n]) 或者 ([1, n+1]),如果最终结果在这两个不存在的值上,那就是没有答案。
因此,使用配套的 (mid) 取法时必须的。
至此,整数域的二分已经讲完,采用" (l = mid + 1, r = mid - 1) " 或 “ (l = mid, r = mid) "的代码形势也很常见,但是在这里不做过多的阐述
实数域上的二分
实数域上的二分比较简单,设置精度,然后直接二分就可以。
while (l + eps < r) {
double mid = (l + r) / 2;
if (calc(mid)) r = mid; else l = mid;
}