二分(数的范围)

整数二分:二分的本质并不是单调性,即有单调性一定可以用二分,但没有单调性也可能可以用二分;二分的本质其实是边界。

简单来说:给定一个区间,在这区间上定义了某种性质,使得在右半边区间是满足的,在左半边区间是不满足的,如果可以找到这个性质,即将整个区间一分为二,使得一半满足,一半不满足,那么就可以采用二分来寻找这个性质的边界。 

既可以二分出红色边界点,也可以二分出绿色边界点,这又是两个不同的模板

模板一(二分红色边界点):

  1.  中间值mid=(l+r+1)/2(思考一下为什么+1),判断中间值是否满足这个性质:if(check(mid))
  2. 如果满足if(true):mid一定在红色区间,答案在[mid,r],更新方式:l=mid
  3. 如果不满足if(false):mid一定在绿色区间,答案在[l,mid-1](因为不满足,所以答案一定不在mid上),更新方式:r=mid-1
//区间[l,r]被划分为[l,mid-1]和[mid,r]时使用;
int bsearch_1(int l,int r){
    while(l<r){
        int mid=(l+r+1)>>1;
        if(check(mid)) l=mid;  //check判断是否满足性质
        else r=mid-1;
    }
    return l;
}


模板二(二分绿色边界点):

  1.  中间值mid=(l+r)/2,判断中间值是否满足这个性质:if(check(mid))
  2. 如果满足if(true):mid一定在绿色区间,答案在[l,mid],更新方式:r=mid
  3. 如果不满足if(false):mid一定在红色区间,答案在[mid+1,r](因为不满足,所以答案一定不在mid上),更新方式:l=mid+1
//区间[l,r]被划分为[l,mid]和[mid+1,r]时使用;
int bsearch_2(int l,int r){
    while(l<r){
        int mid=(l+r)>>1;
        if(check(mid)) r=mid;  //check判断是否满足性质
        else l=mid+1;
    }
    return l;
}


如何分析二分问题:

先写一个check()函数,再思考ture 或 false 情况如何更新,如果更新方式是l=mid,则需要补上+1,如果是r=mid,则不需要补上+1

为什么要补上加一:

举个例子:因为c++里是下取整,当 l=r-1,mid=(l+r)/2下取整等于 l,模板一if(true)中区间[mid,r],更新时,l=mid=l一直不变,造成死循环;补上加一,mid=(l+r+1)/2=r,更新时l=mid=r,[r,r]就停止不会发生死循环

题目:

思路:

  • 元素 k 将整个数组分为左右两边
  • 二分元素 k 起始位置:性质定为q[mid]>=k,所以从k往后所有数都满足>=k ,如果满足性质,则mid在右半边,那么答案(边界)在左半边,并且答案包含mid,所以要更新为[l,mid],r=mid(不补+1),即模板二
  • 二分元素 k 结束位置:性质定为q[mid]<=k,所以从k往前所有数都满足<=k ,如果满足性质,则mid在左半边,那么答案(边界)在右半边,并且答案包含mid,所以要更新为[mid,r],l=mid(补+1),即模板一
  • 数组中不存在 k 元素时:因为最终二分出来的值是从左往右第一个满足>=k的数,如果 k 不存在,那么这个值一定>k,即q[l]!=k

代码: 

#include<iostream>
using namespace std;
const int N=1e5+10;
int n,q;
int s[N];

int main(){
    cin>>n>>q;
    for(int i=0;i<n;i++)cin>>s[i];

    while(q--){
        int k;
        cin>>k;

        //二分出起始边界

        int l=0,r=n-1;
        while(l<r){
            int mid=(l+r)>>1;
            if(s[mid]>=k)r=mid;
            else l=mid+1;
        }

        //数组中没有该元素时

        if(s[l]!=k)cout<<"-1 -1"<<endl;

        else{
            cout<<l<<' ';

            //二分出结束边界

            int l=0,r=n-1;
            while(l<r){
                int mid=(l+r+1)>>1;
                if(s[mid]<=k)l=mid;
                else r=mid-1;
            }
            cout<<l<<endl;
        }
    }
    return 0;
}

总结:

 整数二分都可以用这两个模板解决,主要思想是在一个区间内部去二分答案(边界),每次都要选择答案所在的区间进行下一步操作,保证该区间内一定有答案,当区间长度是1时,区间里的数就是答案。题目可能是无解,但是二分的模板一定是有解的。

 有关算法竞赛的题目会继续更新,欢迎评论交流!

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值