二分法模板及例题(二分答案)

二分法模板及例题(二分答案)

注:二分法只适用于排好序的有序的数列,本篇以从小(左)到大(右)为唯一顺序进行讲解

注:二分答案不同于二分查找,如果你想要学习二分查找,那么这篇文章将对你帮助不大,本文主讲二分答案

个人观点:我认为二分答案与二分查找的区别在于,二分查找主要是寻找某元素是否存在,以找到为算;而二分答案应用较多的是不仅要找到,更要找最优,比如查找一个最大或者最小符合条件的值,那么这一定是一个二分答案题,通常来说,二分答案常见于竞赛题目中,所以竞赛所讲、所用的“二分法”多半是二分答案(法)

(一)、二分法模板
1. 二分取左端:

本方法使用范围/条件:可概述为求符合条件的最小值;由此观之,存在临界最小值满足条件,当大于等于临界值时,都是符合条件的,例如

(本例有些极端,数据偏小,数据量大的时候二分优势明显,这里仅作为例子)

例如:a[10] = {1, 8, 12, 17, 21, 25, 26, 29, 35, 45},求大于15的最小值

显然,答案为17

当我们用二分的时候,这里就有两种情况:

​ 一种是中轴(即mid轴)对应的数值不在范围内,比如上例中,假如mid = 2, a[mid] = 12 < 17

​ 另一种则是中轴对应的数值在范围内,即中轴在满足条件的范围内,如上例,假如mid = 3(a[mid] = 17)或者mid = 4(a[mid] = 21),都满足a[mid] > 17

想要得到正解17,还需要功夫。操作思路如下:

​ 针对上述第一种情况,mid不在范围内说明mid小了,此时就应该放弃查找mid左边的数据,于是更新左端点,使左端点移动至mid位置

​ 针对上述第二种情况, mid在范围内说明满足第一个条件,但是是不是最小那就不知道,这时就不用管它,假装这个mid就是最小,但这还不为结束,因为只是假装,没有验证,只有当左端点不断移动,等于或超过右端点时,就基本确定了,这个mid就是满足条件最小值——因为mid左边不满足第一个条件,而左端于右端重合了,说明该mid对应数值就是临界

​ 端点的移动必然引起mid轴移动,

情况一:上一次mid轴不在范围内,左端点移动至上一次的mid轴,使本次mid轴向右靠近范围;

情况二:上一次mid轴在范围内,右端移动至上一次的mid轴,使本次mid轴向左靠近范围。

​ 为了避免死循环出现,对于情况一,mid轴部长范围了,那么左端点就不必移动至mid处,而是直接移动至mid + 1 处,这样做最大的原因出在C语言整数除法的特性上,比如(1 + 2) / 2 = 1

代码如下:

int bsearch_1(int l, int r)
{
     while (l < r)
     {
           int mid = (l + r) / 2;
           if (judge(mid))  r = mid;//judge()函数判断是否在范围内,为布尔型
           else   l  = mid + 1;//避免死循环
      }
      return l;
}
2. 二分取右端:

右端思路同左端,操作细节略有不同,直接给出代码(既然是模板,就没有什么好解释的了,记下来就完了),代码如下:

int bsearch_2(int l, int r)
{
      while (l < r)
      {
            int mid = (l + r + 1) / 2;//+1避免死循环
            if (judge(mid))  l = mid;
            else  r = mid - 1;
      }
      return 1;
}
(二)、二分法解题

以下问题全部有视频讲解,建议步骤:先看原题,思考做题,视频讲解,代码研读

1. 数的范围

题目一:数的范围

思路 && 代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 100010;
int arry[N];
int n, q, k;

int main(void)
{
    
    scanf("%d %d", &n, &q);
    for (int i = 0; i < n; ++ i)
       scanf("%d", &arry[i]);
    while (q --)
    {
        int l = 0, r = n - 1;
        scanf("%d", &k);
        while (l < r)//套模板,先找左端
        {
            int mid = (l + r) / 2;
            if (arry[mid] >= k)  r = mid;
            else l = mid + 1;
        }
        if (arry[ed] == k)
        {
            cout << r << ' ';
            r = n - 1;
            while (l < r)//再找右端
            {
                int mid = (l + r + 1) / 2;
                if (arry[mid] <= k)  l = mid;
                else  r = mid - 1;
            }
            cout << r << endl;
        }
        else
           cout << "-1 -1" << endl;
    }
    
    return 0;
2. 数的三次方根

题目二:数的三次方根

思路 && 代码如下:

#include <iostream>
#include <cstdio>

using namespace std;

int main(void)
{
    double n, l, r, mid;
    l = -10000, r = 10000;
    cin >> n;
    while (r - l > 1e-8)//当左端l与右端精度差极小时,可认为该值即为三次方根
    {
        mid = (l + r) / 2;
        if (mid * mid * mid >= n)
           r = mid;
        else
           l = mid;
        /*因为是浮点数,不必考虑死循环,每次二分得到的中轴一定不  
        同,不存在(1 + 2) / 2 = 1的情况*/
    }
    printf("%lf", mid);
    
    return 0;
}
3. 机器人跳跃问题

题目三:机器人跳跃问题

思路 && 代码如下:

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;
int n, maxu, a[N];

bool judge(int x)
{
    for (int i = 0; i < n; ++ i)
    {
        x = 2 * x - a[i+1];
        if (x > maxu)  break; 
        if (x < 0)  return false;
    }
    
    return true;
}

int main(void)
{
    
    cin >> n;
    for (int i = 1; i <= n; ++ i)
    {
       scanf("%d", &a[i]);
       if (maxu < a[i])  maxu = a[i];
    }
    int l = 0, r = N;
    while (l < r)//经典二分
    {
        int mid = (l + r) / 2;
        if (judge(mid))  r = mid;
        else  
            l = mid + 1;
    }
    cout << r << endl;
    
    return 0;
}
4. 分巧克力

题目四:分巧克力

视频讲解

思路 && 代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;
const int N = 100010;
int n, m;
int h[N], w[N];

bool judge(int x)
{
    int sum = 0;
    for (int i = 0; i < n; ++ i)
    {
        sum = sum + (h[i] / x) * (w[i] / x);
        if (sum >= m)  return true;
    }
    
    return false;
}

int main(void)
{
    scanf("%d %d", &n, &m);
    for (int i = 0; i < n; ++ i)
       scanf("%d %d", &h[i], &w[i]);
    int s = 1, t = N;
    while (s < t)
    {
        int mid = (s + t + 1) / 2;
        if (judge(mid))  s = mid;
        else  t = mid - 1;
    }
    printf("%d", s);
    
    return 0;
}

本文节选题目全部出自AcWing,如果想要一起学习的伙伴,可以一起学习,点击链接,愿与大家一起进步!如有错误,敬请指正;如有侵权,立即删除!谢谢大家的关心、鼓励与支持!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值