二分算法
整数二分
二分的本质不是单调性。
(有单调性一定可以二分,但是二分可以做的题,不一定需要满足单调性。)
二分的本质是二段性
就是有一个分界点O,分界点左边都是状态x,分界点右边都是状态y。
通过二分就可以找到红色区域的右边界值或者绿色区域的左边界值
每次保证我们区间内有答案。
O点就是我们想求的答案点,绿色是满足性质的区域,红色是不满足性质的区域。
求右分界点模板
当想找不满足性质的边界值(红色区域的右边界值)
例如:
求递增序列中,求小于10的最大值。
check函数就是:\(mid<10\)
过程:
-
找中间值 \(mid =\frac{l+r+1}{2}\) 计算mid时需要加1
-
if(check(mid))等于true或者是false
check(m)检查是不是在红色区间。
-
- 如果在红色区间(true) 正确区间为:\([mid,r]\) ==>
l=mid
- 如果在红色区间(true) 正确区间为:\([mid,r]\) ==>
-
- 否则不在区间返回 : false 正确区间为:\([l,mid-1]\) ==>
r=mid-1
- 否则不在区间返回 : false 正确区间为:\([l,mid-1]\) ==>
为什么需要+1?
原因是如果不加上1,那么mid得到的是下取整的数,那么有可能[m,r]更新过后m会一直等于m(m+1==r的情况)会陷入死循环。
代码:
int bsearch(int l, int r)
{
while(l<r){
int mid=l+r+1 >>1;
if(a[mid]<=k) l=mid;
else r=mid-1;
}
}
求左分界点模板
当想找满足性质的边界值(绿色区域的左边界值)
例如:
求递增序列中,求大于10的最小值。
check函数就是:\(mid>10\)
过程:
-
找中间值 \(mid =\frac{l+r}{2}\)
-
if(check(mid))等于true或者是false
check(m)是检查m是在满足绿颜色区间的性质(检查是不是在绿色区间)
-
- 如果是true 正确区间:\([l,mid]\) ==>
r=mid
- 如果是true 正确区间:\([l,mid]\) ==>
-
- 如果是false 正确区间:\([mid+1,r]\) ==>
l=mid+1
- 如果是false 正确区间:\([mid+1,r]\) ==>
代码:
int bsearch(int l, int r)
{
while(l<r){
int mid=l+r >>1;
if(a[mid]>=k) r=mid;
else l=mid+1;
}
}
模板题
题目:
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回 -1 -1。
代码:
const int N = 2e6 + 10;
int n,m, a[N];
void solve(){
cin>>n>>m;
for(int i=0;i<n;i++)
cin>>a[i];
while(m--){
int k;cin>>k;
int l,r;
//找左边界
l=0,r=n-1;
while(l<r){
int mid=l+r >>1;
if(a[mid]>=k) r=mid;
else l=mid+1;
}
int ans1=r;
//找右边界
l=0,r=n-1;
while(l<r){
int mid=l+r+1 >>1;
if(a[mid]<=k) l=mid;
else r=mid-1;
}
int ans2=r;
if(a[ans1]==k) {
cout<<ans1<<" "<<ans2<<endl;
}
else cout<<"-1 -1"<<endl;
}
}
浮点数二分
和整数一样,while循环时换成r - l > 精度
即可。
而且因为是浮点数,不需要考虑 +1 -1 之类的。
代码:
int bsearch(double l, double r)
{
while(r - l > 精度){
double mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid;
}
}
应用
平均值问题
平均值问题一般都是二分。
问题描述:
给定序列a,判断是否存在一个方案,使其平均值大于等于avg
方法:
二分平均值,使a的所有元素减去avg。
看否是存在长度大于F的一段串的和 \(>=0\)