二分查找(模板题笔记)

二分算法

目录

二分查找(模板题笔记)

整数二分算法

浮点数二分算法

二分答案

使用条件

二分查找(模板题笔记)

二分查找又称折半查找,它是一种效率较高的查找方法。

二分搜索法,是通过不断缩小解可能存在的范围,从而求得问题最优解的方法。在程序设计竞赛中,经常可以见到二分搜索法和其他算法结合的题目。 --挑战程序设计竞赛

二分查找的时间复杂度为 O(logn),与线性查找的 O(n) 相比速度上得到了指数倍提高(x=log₂n,则 n=2^x )

二分 - OI Wiki

整数二分算法

789. 数的范围 - AcWing

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

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

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

输入格式

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

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

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

输出格式

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

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

数据范围

1 ≤ n ≤ 100000
1 ≤ q ≤ 10000
1 ≤ k ≤ 10000

输入样例:

6 3
1 2 2 3 3 4
3
4
5

输出样例:

3 4
5 5
-1 -1

解答

手敲 

#include 
using namespace std;
#define IOS ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define endl "\n"
#define ll long long 
const ll N = 100005;
ll n, q, k, a[N];
signed main() {
    cin >> n >> q;
    for (int i = 0; i < n; i++)cin >> a[i];
    while (q--) {
        cin >> k;
        int l = 0, r = n - 1;
        while (l < r) {
            int mid = l + r >> 1;   //即(l + r)/2 
            if (a[mid] >= k)r = mid;    //k <= a[mid],a[mid+x] a[mid]右边都为true,则缩小右边界(a[mid]可能为k,故无需+1)
            else l = mid + 1;           //a[mid-x],a[mid] < k  a[mid]左边都为if(false),缩小左边界(a[mid]一定不为左边界,故+1)
        }
        if (a[l] != k) { cout << -1 << " " << -1 << endl; continue; }
        else {
            cout << l << " ";
            l = 0, r = n - 1;
            while (l < r) {
                int mid = l + r + 1 >> 1;  //因为是向下取整,有可能使left = mid而死循环,则+1避免
                if (a[mid] <= k)l = mid;   //a[mid-x],a[mid] <= k a[mid]以及左边都为true,缩小左边界(a[mid]可能为k,故无需+1)
                else r = mid - 1;             //k < a[mid],a[mid + x] a[mid]右边都为false 缩小右边界
            }
            cout << l << endl;
        }
    }
    return 0;
}

使用upper_bound()和lower_bound(); (记得先sort)

C++ lower_bound()函数 -指向首个不小于 value 的元素的迭代器,若找不到返回 last 。(返回大于等于value的最小数的迭代器)

C++ upper_bound()函数 -指向首个大于 value 的元素的迭代器,若找不到返回 last 。(返回大于value的最小数的迭代器)

C++ equal_range()函数 //std::equal_range - C++中文 - API参考文档

C++ binary_search()函数

#include
using namespace std;
#define IOS ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define endl "\n"
#define ll long long
//const ll MAXN = 2 * (1e6 + 10);
const int MAX = 100005;
ll n, q, k, a[MAX];
signed main() {
    IOS;
    cin >> n >> q;
    for (int i = 0; i < n; i++)cin >> a[i];
    for (int i = 0; i < q; i++) {
        cin >> k;
        int index1 = lower_bound(a, a + n, k) - a;
        int index2 = upper_bound(a, a + n, k) - a;
        if (index1 != index2)cout << index1 << " " << index2 -1 << endl;
        else cout << -1 << " " << -1 << endl;
    }
    return 0;
}

使用equal_range();

#include
using namespace std;
#define IOS ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define endl "\n"
#define ll long long
//const ll MAXN = 2 * (1e6 + 10);
const int MAX = 100005;
ll n, q, k, a[MAX];
signed main() {
    IOS;
    cin >> n >> q;
    for (int i = 0; i < n; i++)cin >> a[i];
    for (int i = 0; i < q; i++) {
        cin >> k;
        pair b = equal_range(a, a + n, k);
        if (b.first - a != b.second - a)cout << b.first - a << " " << b.second - a - 1 << endl;
        else cout << -1 << " " << -1 << endl;
    }
    return 0;
}

模板

bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 可行解在右侧,满足条件时缩小右区间
int bsearch_1(int l, int r){        
    while (l < r){
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    
        else l = mid + 1;
    }
    return l;
}    
// 可行解在左侧,满足条件时缩小左区间 
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;
}

来源:https://www.acwing.com/blog/content/277/

浮点数二分算法

790. 数的三次方根 - AcWing

给定一个浮点数 n,求它的三次方根。

输入格式

共一行,包含一个浮点数 n。

输出格式

共一行,包含一个浮点数,表示问题的解。

注意,结果保留 6 位小数。

数据范围

−10000 ≤ n ≤ 10000

输入样例:

1000.00

输出样例:

10.000000

解答

#include
using namespace std;
#define IOS ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define endl "\n"
#define ll long long 
//const ll MAXN = 2 * (1e6 + 10);
const int MAX = 100005;
double n, mid, l=-10000, r=10000,eps = 1e-8;
signed main() {
    IOS;
    cin >> n;
    while (r - l > eps) {
        mid = (l + r) / 2;
        //浮点除法不会取整,所以mid,l,r,都不用加1或减1
        if (mid * mid * mid < n)l = mid;    //如果mid < ³√n 中点在根左边,缩小左边界
        else r = mid;                        //否则³√n <= mid 中点在根右边,缩小右边界
    }
    printf("%.6lf", l);
    return 0;
}

模板

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;
}

来源:https://www.acwing.com/blog/content/277/

二分答案

如果答案具有单调性且有范围,那么就可以二分枚举答案,在答案合法的条件下不断逼近最优(如最大值最小或最小值最大),最后一个合法的答案就是最优解,这便是二分答案 -二分答案入门 - Santiego - 博客园

即通过不断check()并且二分 从而逼近最优解

二分答案算法教程(超详细) - 掘金

使用条件

1、答案在一个区间内(一般情况下,区间会很大,暴力超时)
2、直接搜索不好搜,但是容易判断一个答案可行不可行
3、该区间对题目具有单调性,即:在区间中的值越大或越小,题目中的某个量对应增加或减少。
此外,可能还会有一个典型的特征:求…最大值的最小 、 求…最小值的最大。

P1024 - 一元三次方程求解 - 洛谷

题目描述

有形如:ax^3 + bx^2 + cx + d = 0 这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在 -100 至 100 之间),且根与根之差的绝对值 ≥1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 2 位。

提示:记方程 f(x) = 0,若存在 2 个数 x1 和 x2,且 x1, f(x1)×f(x2)<0,则在 (x1,x2) 之间一定有一个根。

输入格式

一行,4 个实数 a, b, c, d

输出格式

一行,3 个实根,从小到大输出,并精确到小数点后 2 位。

输入输出样例

输入 #1

1 -5 -4 20

输出 #1

-2.00 2.00 5.00

解答

#include
using namespace std;
double a, b, c, d;
double fx(double x) {
    return a * x * x * x + b * x * x + c * x + d;
}
signed main() {
    double l, r, mid, x1, x2;
    scanf("%lf%lf%lf%lf", &a, &b, &c, &d);
    for (int i = -100; i < 100; i++) {
        l = i;
        r = i + 1;
        x1 = fx(l);
        x2 = fx(r);
        if (!x1) {
            printf("%.2lf ", l);
        }
        if (x1 * x2 < 0) {     //区间内有根。
            while (r - l >= 0.001) {
                mid = (l + r) / 2;
                if (fx(mid) * fx(r) <= 0)
                    l = mid;
                else
                    r = mid;
            }
            printf("%.2lf ", r);//输出右端点。
        }
    }
    return 0;
}

P1873 - EKO / 砍树 - 洛谷

题目描述

伐木工人 Mirko 需要砍 M 米长的木材。对 Mirko 来说这是很简单的工作,因为他有一个漂亮的新伐木机,可以如野火一般砍伐森林。不过,Mirko 只被允许伐一排树。

Mirko 的伐木机工作流程如下:Mirko 设置一个高度参数 H(米),伐木机升起一个巨大的锯片到高度 H,并锯掉所有树比 H 高的部分(当然,树木不高于 H 米的部分保持不变)。Mirko 就得到树木被锯下的部分。例如,如果一排树的高度分别为 20,15,10 和 17,Mirko 把锯片升到 15 米的高度,切割后树木剩下的高度将是 15,15,10 和 15,而 Mirko 将从第 1 棵树得到 5 米,从第 4 棵树得到 2 米,共得到 7 米木材。

Mirko 非常关注生态保护,所以他不会砍掉过多的木材。这也是他尽可能高地设定伐木机锯片的原因。请帮助 Mirko 找到伐木机锯片的最大的整数高度 H,使得他能得到的木材至少为 M 米。换句话说,如果再升高 1 米,他将得不到 M 米木材。

输入格式

第 1 行 2 个整数 N 和 MN 表示树木的数量,M 表示需要的木材总长度。第 2 行 N 个整数表示每棵树的高度。

输出格式

1个整数,表示锯片的最高高度。

输入输出样例

输入 #1

4 7 20 15 10 17

输出 #1

15

输入 #2

5 20 4 42 40 26 46

输出 #2

36

解答

//把可能取值(1-longest)不断询问中点是否满足,不断递归,最终会得到一个满足值的边界(即结果)
#include
using namespace std;
typedef long long ll;
ll N, M, leftt, longest, trees[1000008];

bool check(int mid) {
    ll sum = 0;
    for (int i = 1; i <= N; i++)
        if (trees[i] > mid)
            sum += trees[i] - mid; //高的部分累加 
    return sum < M;
}

int main() {
    scanf("%lld%lld", &N, &M);
    for (int i = 1; i <= N; i++) {
        scanf("%lld", &trees[i]);
        longest = max(longest, trees[i]);//找到最长木材 
    }
    while (leftt < longest) {
        int mid = leftt + longest >> 1;
        if (check(mid)) longest = mid;
        else leftt = mid + 1;
    }
    cout << leftt - 1;
    return 0;
}

还不懂可以看这个题解 剪绳子-AcWing

你可能感兴趣的:(#,算法,算法,c++)