lower_bound 和 二分

老是写不好二分,特此学习总结一下。

lower_bound 和 upper_bound

这两个函数基本上能替代手写的二分查找,但是千万别弄错俩函数的作用。
lower_bound 返回数组中第一个不小于目标值的元素的iterator,(第一个>=target的数)
upper_bound 返回数组中第一个大于目标值的元素的iterator, (第一个>target的数)

手写二分查找

主要是熟练使用二分,不单单只是用来查找一个数 ,而是能指哪打哪,用来解决带有单调性的数组问题,这才是二分的威力。

经典二分模板, 一个是区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用, 一个是区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用。当数组为前段false,后端true时候,用(1),前段true,后段false时,用(2)

bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

// 作者:yxc
// 链接:https://www.acwing.com/blog/content/277/
// 来源:AcWing
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

二分查找 :数的范围

经典题AcWing 789. 数的范围,给定数组,如1 2 2 3 3 4, 和target, 如3,找到3的范围[3,4]。

可见,本题需要找到arr[low] <= target的下界,还要找到arr[up] >= target的下界,根据区间是前false后true还是前true后false来使用二分方法。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

vector<int> arr {};
int n, m, target;


// 两种方法用来找符合checker的最左边以及符合checker的最右边。
int low(int l, int r, int target) {
    while(l<r) {
        int mid = l+r>> 1;
        if (arr[mid]>=target) r = mid;
        else l = mid+1;
    }
    return l;
}

int up(int l, int r, int target) {
    while (l<r) {
        int mid = l+r+1 >> 1;
        if (arr[mid]<=target) l = mid;
        else r = mid-1;
    }
    return r;
}

int main() {
    cin >> n >> m;
    arr.resize(n);
    
    for (int i=0; i<n; i++) cin >> arr[i];
    
    for (int i=0; i<m; i++) {
        cin >> target;
        int lower = low(0, n-1, target);
        int upper = up(lower, n-1, target);
        if (arr[lower]!=target) cout << "-1 -1" << endl;
        else 
        cout << lower<< " " << upper << endl;
    }
}

二分巧用-最大上升子序列


数组坐标i代表长度为i+1的子序列。长度为i+1的子序列,最后的数为arr[i]。如果如果遇到arr.back() < val, 则有更大子序列,arr.push_back(val)。反之对前面的第一个大于等于val的元素进行更新。
arr : 1 3 4
进入2,则最优长度为2的数列的结尾数为2 -> 即 1 2 4

#include <vector>
#include <cstdio>
using namespace std;

int n, val, len;
vector <int> stk;

int myLowerBound(int l, int r, const vector<int> &stk, int val) {
    while (l<r) {
        int mid = (l+r) / 2;
        if (stk[mid] >= val ) r = mid;
        else l = mid+1;
    }
    return l;
}

int main() {
    scanf("%d", &n);
    stk.reserve(n);
    scanf("%d", &val);
    stk.push_back(val);
    
    for (int i=1; i<n; i++) {
        scanf("%d", &val);
        if (stk.back() >= val) { // 可以跳过 == val
            int idx = myLowerBound(0, stk.size(), stk, val);
            stk[idx] = val; // stk[0] = val, if stk[0] > val, otherwise, val ~ [stk[i], stk[j]] 和 (stk.back(), +)
        }
        else stk.push_back(val);
    }
    
    printf("%d", stk.size());
}

浮点二分

模板, 主要就是加入了eps判断是否r和l相等

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

double 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;
    }
    return l;
}

例子:求三次方根

设置精度 -> cout.setf(ios::fixed); cout << setprecision(6) << f << endl; // iomanip
注意,出现精度问题要换double, 如果是用cstdio,要用%lf

#include <cstdio>
#include <cmath>
#include <iostream>
// #include <iomanip>
// cout << setiosflags(ios::fixed) <<setprecision(6) << l;
// round会四舍五入,printf也会四舍五入]
// scan double -> %lf
using namespace std;

double n;
double l = -30., r = 30.;
double eps = 1e-7;
int main() {
    scanf("%lf", &n);

    while (r-l > eps) {
        double mid = (l+r) / 2;
        if (mid*mid*mid >= n) r = mid;
        else l = mid;
    }
    
    printf("%.6lf", l);
}
posted @ 2020-09-06 19:20  linsinan1995  阅读(393)  评论(0编辑  收藏  举报