二分

907 阅读3分钟

第一次了解二分是在高中的数学课,当时没觉得多难,毕竟思想是很简单的。但在代码实现上却会遇到很多困难,最常见的也是最难的就是整数二分的边界问题。

整数二分

主要思路

整数二分的思路是这样的:将整数序列分为两端,分界满足的条件,将区间化为左右两个部分,左边满足这个条件,右边不能满足这个条件(或者相反)。此时就用二分来来查找左右两部分的边界。所以所谓二分算法,就是我们知道当前的候选区间中,一定存在我们要找到的答案,而且我们发现这个区间拥有单调性质此类的性质,那么我们可以不停地缩减候选区间的范围,达到排除无用答案的效果,也就是找到正确答案(边界)。

整数二分是有两个边界的,分别是左边的边界与右边的边界,即左右两半部分的边界不是同一个点,而是相邻的2个点,就像下面这个草图:

image.png

这就让我们的二分有两个模板,一个是找左边红色的边界,一个是找右边绿色的边界。实际运用时,先不考虑用哪个模板,而是先写check()函数,然后写模板(不考虑mid是否+1),写到if(check(mid))时,再考虑满足check(mid)条件的段是在哪一边:如果在左边红色,则填l = mid;如果在右边,则填r = mid。然后填出else的部分,最后看else后如果填写的是-,则要在mid声明处+1;反之不补。(简单的记忆就是,仅当采用l = mid这种更新方式时,计算mid时需要加1。)

模板

bool check(int x){/*检查x是否满足某个条件*/};
//二分
void bsearch_one(int l,int r){
    while(l < r){
        int mid = (l + r) >> 1;
        if(check(mid)) r = mid;//检查
        else l = mid + 1;
    }
}

void bsearch_two(int l,int r){
    while(l < r){
        int mid = (l + r + 1) >> 1;
        if(check(mid)) l = mid;//检查
        else r = mid - 1;
    }
}

例子

给定一个按照升序排列的长度为 nn 的整数数组,以及 qq 个查询。

对于每个查询,返回一个元素 kk 的起始位置和终止位置(位置从 00 开始计数)。

如果数组中不存在该元素,则返回 -1 -1

输入格式

第一行包含整数 nn 和 qq,表示数组长度和询问个数。

第二行包含 nn 个整数(均在 1∼100001∼10000 范围内),表示完整数组。

接下来 qq 行,每行包含一个整数 kk,表示一个询问元素。

输出格式

共 qq 行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回 -1 -1

数据范围

1≤n≤1000001≤n≤100000
1≤q≤100001≤q≤10000
1≤k≤100001≤k≤10000

输入样例:

6 3
1 2 2 3 3 4
3
4
5

输出样例:

3 4
5 5
-1 -1
//foreverking
#include <vector>
#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;

const int N = 100001;

int n,q,x;
int a[N];

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

    while(q--){
        cin >> x;
        int l = 0,r = n - 1;
        while(l < r){
            int mid = (l + r) >> 1;
            if(a[mid] >= x) r = mid;
            else l = mid + 1;
        }

        if(a[l] != x) cout << "-1 -1" << endl;
        else{
             cout << l << ' ';
             //找左边界,第二个模板
             int l = 0,r = n - 1;
             while(l < r){
                 int mid = (l +  r + 1) >> 1;
                 if(a[mid] <= x) l = mid;
                 else r = mid - 1;
             }
             cout << l << endl;
        }   
    }

    return 0;
}

分别找出左边边界和右边边界就是答案,找不到就输出-1。

浮点二分

浮点二分就简单许多,因为没有边界问题。

模板

//浮点二分
bool check(double x) {/* ... */} // 检查x是否满足某种性质

void bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
}