(参考:李煜东《算法竞赛进阶指南》:0x04 二分)
我们都知道,实数域上的二分的写法非常简单,确定好精度很省心。
而整数集上的二分,是需要关注起始边界、终止边界、中点选择、左右区间取舍时的开闭情况的。
《算法竞赛进阶指南》上的整数集合上二分写法和我平时喜欢使用的是一样的,因此进行记录,以便后续参考。
本文所记录的二分写法,需要保证答案处于闭区间 $[l,r]$ 内、循环以 $l=r$ 结束、每次二分的中点 $mid$ 会归属于左右区间中的一个。
(1)在单调递增序列 $a$ 中查找,第一个满足大于等于 $x$ 的数:
while(l<r) { int mid=(l+r)>>1; if(a[mid]>=x) r=mid; else l=mid+1; } return l;
(2)在单调递增序列 $a$ 中查找,最后一个满足小于等于 $x$ 的数:
while(l<r) { int mid=(l+r+1)>>1; if(a[mid]<=x) l=mid; else r=mid-1; } return l;
(注:在计算 $mid$ 使用算术右移而非除法,是因为算术右移的结果始终是向下取整,而除法是向零取整,使用算术右移可以使得区间为负数时也可以正常二分。若我们能保证在非负整数集合上二分,除 $2$ 也是可以的。)
另外,我们注意到:
对于写法(1), mid=(l+r)>>1 不会取到 $r$。因此,若我们将初始的二分范围 $[l,r]$ 扩大到 $[l,r+1)$,一旦二分无解,则最终返回的下标将会是 $r+1$。
对于写法(2), mid=(l+r+1)>>1 不会取到 $l$。因此,若我们将初始的二分范围 $[l,r]$ 扩大到 $(l-1,r]$,一旦二分无解,则最终返回的下标将会是 $l-1$。
这样一来,便可以轻松地判断二分是否有解。