二分
介绍
相信大家小时候都玩过猜数字的游戏,一般来说,大家都是从中间开始猜,比如猜0~100之间的整数,先猜50,小了再猜75,大了再猜35。我们在玩这个游戏时就使用了二分这个思想,在算法竞赛中,我们对具有单调性的问题便可以使用二分,是一种非常好用的算法,但是二分里面的坑也非常多,稍有不慎便会漏掉答案或陷入死循环
整数二分
此为二分的示意图,我们在对问题进行求解的时候,先分析问题中有什么性质可以利用,根据这个性质找两个区间的边界,使得左边不满足或满足这个性质,右边满足或不满足这个性质,注意两个区间并没有交点(一个点不可能既满足又不满足)
根据区间究竟满足还是不满足,二分也有两个模板(一种 \(mid\) 落在左区间,另一种 \(mid\) 落在右区间):
第一种模型
这种模型 \(mid=(l+r)/2\),这里无需考虑边界。
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
判断 \(mid\) 是否满足性质:
如果满足说明答案包括 \(mid\) \(r=mid\)
如果不满足说明答案包括 \(mid\) \(l=mid+1\)
第二种模型
这种模型的 \(mid = (l+r+1)/2\) 。这里因为计算机会自动去尾,例如当 \(l=0,r=1\) 时,如果不加一 \(mid\) 便会变成0,\(l=mid\) ,便会陷入死循环
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;
}
判断 \(mid\) 是否满足性质:
如果满足说明答案包括 \(mid\) \(r=mid\)
如果不满足说明答案不包括 \(mid\) \(l=mid+1\)
浮点数二分
浮点数二分相对简单,不像整数二分需要考虑边界问题
精度问题
两种方式:
- 设置一个精度 \(eps\) 循环条件是 \(r-l>eps\) 保留 \(k\) 位小数时 \(eps=10^-\)\(^(\)\(^k\)\(^+\)\(^2\)\(^)\)
- 循环100次
每次根据 \(mid\) 确定是 \(r=mid\) \(l=mid\) 还是 \(l=mid\) \(r=mid\) 就行了
如何使用
关于无解的判断
\((l+r+1)/2\) \(mid\) 取不到 \(l\),因为 \(l=mid+1\) ,当 \(l=r\) 就退出,假设 \(mid\) 能取到 \(r\) ,那么 \(l=r+1\),与循环退出的条件不符
\((l+r)/2\) \(mid\) 取不到 \(r\),原因同上
两种判断方法:
因为二分保证有解,在循环结束时一定停在一个点上,所以衍生出了两种方法:
1、看这一个点是否满足要求,不满足就无解
2、\((l+r+1)/2\) 将原本 \([1,n]\) 的区间扩大到 \([1,n+1]\),如果最后停在了越界的下标上说明无解(其他点都不满足要求)
\((l+r)/2\) 将 \([1,n]\) 扩大到 \([0,n]\),与上一个同理
适用场景
区间 \([l, r]\) 被划分成 \([l, mid]\) 和 \([mid + 1, r]\) 时使用 第一个模板
区间 \([l, r]\) 被划分成 \([l, mid - 1]\) 和 \([mid, r]\) 时使用 第二个模板
\((l+r)/2\) 适用于左区间不满足,右区间满足
\((l+r+1)/2\) 适用于左区间不满足,右区间满足
例题引入
给定一个数组,数组里的元素从小到大递增,找到 \(\geq x\) 的数中最小的一个
给定一个数组,数组里的元素从小到大递增,找到 \(\leq x\) 的数中最小的一个
详解:
当我们遇到最大最小这类字眼的时候,一般要使用二分,当然,二分有两个模板,究竟如何考虑呢
首先,数组是单调递增的,我们可以使用二分
如果要找 \(\leq x\) 的数,那么这个数应该比较小,我们应该在左边查找,所以我们使用第一个模板
如果要找 \(\geq x\) 的数,那么这个数应该比较大,我们应该在右边查找,所以我们使用第二个模板
二分查找
二分查找也被叫做折半查找,顾名思义,就是每次查找去掉不符合条件的一半区间,直到找到答案(整数二分)或者和答案十分接近(浮点二分)。在阅读题面时,我们要关注题目是最小值最大化还是最大值最小化
二分答案
二分答案适用于当答案属于一个区间,这个区间比较大,直接暴力会超时,最重要的是区间满足单调性,当这个答案越大(或越小),题目中的一个变量也会随之增大(或减少),通过check
函数进行检验答案是否正确,根据结果放弃右半部分或左半部分