前言

博主在这几天刷算法的时候,被一些二分答案的题目几尽折磨,在苦苦刷题的时候从yxc大佬那里学到了新的二分模板,在对这个模板深入实践后,愈发感觉这个模板运用好几乎万能,所以在此时记录下我的一些新的理解,并对之前的博客相互印证。(二分查找细节讲解~~~~阅读量好少支持一下吧 没看过的可以看下,相信对你有帮助)

一、什么是二分答案

比如说你要从一本英汉词典上查一个单词,你从头到尾一页一页的翻着找,这样找可以保证一定能找到,但是最坏情况你要把整本词典都翻一遍,那就麻烦了。

那有什么改进的方法吗?当然有。

考虑把这个词典从中间分开,看一下中间那一页的主要单词都是啥,然后去判断我要找的单词应该在左半部分还是右半部分,再去那一部分考虑怎么找就好了。同样的,在另一部分也是要进行划分并且判断的操作。这样一直进行下去,便能很快的找到答案,而且根本不需要翻过整个词典来。

我们把这个方法叫做“二分答案”。顾名思义,它用二分的方法枚举答案,并且枚举时判断这个答案是否可行。

二、算法介绍

1.使用条件和适用情况

二分并不是在所有情况下都是可用的,使用二分需要满足两个条件。一个是有界,一个是单调。

1.二分一般用来解决最优解问题
二分答案应该是在一个单调闭区间上进行的。也就是说,二分答案最后得到的答案应该是一个确定值,而不是像搜索那样会出现多解。所以我们二分的结果必定是一个最优解。

2.请大家务必弄清可行解和最优解的概念,这是我们掌握二分的关键
可以这样想,在一个区间上,有很多数,这些数可能是我们这些问题的解,换句话说,这里有很多不合法的解,也有很多合法的解。我们只考虑合法解,并称之为可行解。考虑所有可行解,我们肯定是要从这些可行解中找到一个最好的作为我们的答案, 这个答案我们称之为最优解。最优解一定可行,但可行解不一定最优。

3.需要满足单调性的要求
我们假设整个序列具有单调性,且一个数x为可行解,那么一般的,所有的x’(x’<x)都是可行解。并且,如果有一个数y是非法解,那么一般的,所有的y’(y’>y)都是非法解。这也是决定了我们二分的时候,答案应该往左查找还是往右查找。

2.代码模板

我们的模板只有两个,我们只需要运用好这两个模板,可以解决掉几乎所有的二分。在此我们先介绍模板和怎么去运用,在最后我们在说明一下这份模板的一些细枝末节。

第一个模板是往左边查找,也就是答案包括在左边的情况

	while(l < r){
            int mid = (l + r) / 2;
            if(check(mid))
                r = mid;
            else
                l = mid + 1;
        }

那很明显,这个就是往右边查找,也就是答案包括在右边的情况

	while(l < r){
           	int mid = (l + r + 1) / 2;
            if(chech(mid))
               	 l = mid;
             else
                 r = mid - 1;
         }

3.例题解析

好了上面说了那么多废话,大家估计可能也听得云里雾里的,现在我们直接运用在实战里,给大伙看个明白。

#P2440 木材加工
这一题是很经典的一道二分答案的题目,也可以说是很多二分的母题了。
数据范围是10^5,也就是十万的范围,在c/c++里面评测机一秒数据大概是能处理10e8的数据,也就是说本题直接暴力O(n2) 的算法肯定会超时的,所以我们用二分来解决这个题目。
在这里插入图片描述
解题思路: 看完题意,可能很多同学都是啊?这怎么二分啊。我们没有目标值要查找啊。别担心,刚接触二分答案没有思路是很正常的,和二分查找不一样,二分答案是一种思想,只要我们的答案满足在目标范围内单调,而且范围有界,我们就可以用二分思想

在这个题目里边,我们的目标值就是l,即每段小木头的最大长度,而它的范围,就是[0,max(L[i])] 。一段都没法切,那不就是0吗?能切的最大长度,那不就是那根最长的吗,max(L[i])。那我们二分模板中的l = 0,r = max(L[i])。那我们每次二分,只要判断这个长度能不能满足切出k段相同的木头,不就可以了吗?思路理顺后,我们来看看模板应该用哪个?也就是当我们找到一个可行解的时候,是应该往左呢,还是应该往右呢。根据题意很明显我们应该往右对不对?因为整个范围满足单调递增,而我们查找到一个可行解时,那么它不一定是最优解,我们要求的更大的最优解,那就应该往右查找。
所以选用往右边查找,当check为真时,往右查找。

	while(l < r){
           	int mid = (l + r + 1) / 2;
            if(chech(mid))
               	 l = mid;
             else
                 r = mid - 1;
         }

最后附上题解,数据有点大,所以要记得使用longlong

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef long long ll;
ll a[N];
ll n,m;
bool check(ll h)
{
    int res = 0;
    for (int i = 0 ; i < n ; i++ )
        res += (a[i] / h);      //看看每一段木头能切成几段 h 长的木段
    if(res >= m) return 1;
    else return 0;
}
int main()
{
   cin >> n >> m;
   ll l = 0, r = 0;
   for (int i = 0 ; i < n ; i++ ){
        cin >> a[i];
        r = max(a[i],r);
   }
   while(l < r)
   {
       ll mid = (l + r + 1) / 2;
        if(check(mid))
            l = mid;
        else 
            r = mid - 1;
   }
   cout<<l<<endl;
   return 0;
}

例题2.
机器人跳跃问题(来源:今日头条笔试题)
在这里插入图片描述
解题思路: 本题也是一个二分的问题,由题意很容易看出,我们要的目标值就是初始能量e,范围就是[0,1e5],所以我们只要二分这个范围,然后每次判断一下,查找到的解满不满足可行解,如果是可行解,那么就往左边查找最优解。所以我们选用的模板是第一个模板

	while(l < r){
            int mid = (l + r) / 2;
            if(check(mid))
                r = mid;
            else
                l = mid + 1;
        }

check函数的判断就很简单了,但是有个细节就是,根据题目给的公式,推出无论是升高还是降低都是e = 2 * e - a[i];且记得如果每次e都会乘以2,所以记得满足情况后就及时退出,否则longlong也会爆数据。

#include<iostream>
using namespace std;
const int N = 100000;
int a[N + 5];
int n;
bool check(int e)
{
    for(int i = 0;i < n;i++ ){
        e = 2 * e - a[i];
        if(e<0) return false;
       	if(e > 1e5) return true; // 超过最大值就返回真,如果不设置的话,很容易爆int和long long
    }
    return true;
}
int main()
{
    cin >> n; 
    for(int i = 0;i < n;i++ ) cin >> a[i];
    int l = 0,r = 1e5;
    while(l < r)
    {
        int mid = (l + r) / 2;
        if(check(mid))
            r = mid; 
        else
            l = mid + 1;
    }
    cout<<l<<endl;
    return 0;
}

看完上面两个例题,相信大家应该能有所理解了,在最后我们解释一下模板里的代码细节。

为什么这两个模板能解决全部的二分呢?
答案是因为这两个模板的搜索范围,刚刚好可以覆盖了全部的二分情况,在之前我们讲过了二分的边界细节(不知道的可以去看下),这两个都是把查找目标的情况也糅合了进来。

为什么第二个模板需要mid = (l + r + 1) / 2?
这个相信大家都有疑问吧,大家可以试下如果你不+1的话,二分就会陷入死循环。因为这个二分范围是[l,r),r目标值没有包括进来,在向右二分时,无论如何循环没办法取到最右值,所以需要查找时+1。

总结

最后我们总结一下,二分答案我们只需要找准目标值和目标边界,在根据题目要求选定模板,就能解决问题了。不过文章内容说的都是查找要求目标值的情况的,还有一些二分题目是不包括目标值的,比如蓝桥杯的递增三元组,但这类题目很少,只要把这两个模板掌握好,同样能处理好别的情况。
在这里插入图片描述
码字不易,希望对大家有帮助,如果觉得还不错的话,请给博主点个关注或👍。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐