【基础算法】二分
4.二分
适用条件:具有单调性的问题。
4.1.STL
在从小到大的排序数组中:
lower_bound(begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound(begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
在从大到小的排序数组中,重载lower_bound()和upper_bound():
lower_bound(begin,end,num,greater<type>()):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound(begin,end,num,greater<type>()):从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
4.2.整数集合上的二分
4.2.1.整数集合上的二分
-
l=mid?r=mid?
令长度为2的区间l=1,r=2。看是l=mid还是r=mid会缩小为长度为1区间。
-
保左?保右?
代码
令长度为2的区间l=1,r=2。如果mid是偏向l(r),则该二分是保左(右)。
如果mid是恰好可行的(==)且保左(右),就令r=mid(l=mid)。(因为mid本身就是偏左(右))
问题
保左(右):边界mid在答案位置ans的左(右)边。 此时l(r)会向mid的右(左)边一单位跳到答案位置。
4种情况(0代表check(mid)=false,1代表true):01找最后一个0、01找第一个1、10找最后一个1、10找第一个0。
根据问题选择保左的二分还是保右的二分。
-
无解?
对于保左写法(mid=(l+r)>>1),mid不会取到r这个值,因此可以把最初的二分区间[1,n]扩大为[1,n+1],若最终l==n+1,无解。
对于保右写法(mid=(l+r+1)>>1),mid不会取到l这个值,因此可以把最初的二分区间[1,n]扩大为[0,n],若最终l==0,无解。
在单调序列a中查找≥x最小的一个(x或x的后继、01找第一个1、10找第一个0),保左:
while(l<r)
{
int mid=(l+r)>>1;
if(a[mid]>=x) r=mid;
else l=mid+1;
}
return a[l];//思维:此时l==r
在单调序列a中查找≤x最大的一个(x或x的前躯、01找最后一个0、10找最后一个1),保右:
while(l<r)
{
int mid=(l+r+1)>>1;
if(a[mid]<=x) l=mid;
else r=mid-1;
}
return a[r];//思维:此时l==r
4.2.2.二分答案转为判定
这个思想在很多算法中都有涉猎。
适用条件:具有“单调性”值域的问题:
设定义域为该问题下可行方案,则值域为该方案的评分,最优解就是评估分最高S的方案。\(\forall x>S\),都不存在一个合法的方案达到x分;\(\forall x≤S\),一定存在一个合法的方案达到或超过x分。
又或者说方案一段全部合法另一段全部不合法。
把最优性问题转化为可行性问题,简化题目。
注意保左保右的问题。详见上文。
技巧
- 求绝对值|c-x|最小值:可拆成小于c的最大x和大于c的最小x两个子问题。
- 求check()方案时,要在
while(l<r)循环后check(l),不可以在while(l<r)循环内的if(check(mid))后记录方案,因为有可能mid一直非法到最后l=r=1就没有记录方案了。
4.3.实数域上的二分
while(r-l>EPS)
{
double mid=(l+r)/2;
if(check(mid)) l=mid;//mid还需要再大一点
else r=mid;//mid还需要再小一点
}
return l;//思维:此时l==r。偶尔取l(或r)会有精度问题,需要换着取r(或l)试试

浙公网安备 33010602011771号