二分
定义:二分的基础用法是在单调序列或者单调函数中进行查找。
根据复杂度理论,我们可以通过将求解改为判定的方法,优化算法。这是一种非常基础,又比较容易写错的算法。本文将阐述一种常见的二分方法。
整数集合上的二分
保证最终答案处于闭区间 $ [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;
}