二分算法模板(整数型和浮点型)

目录

模板

例题:

1.double二分

思路

2.二分插入排序

3.二分查找左右边界


模板

 二分模板一共有两个,分别适用于不同情况。
算法思路:假设目标值在闭区间[l, r]中, 每次将区间长度缩小一半,当l = r时,我们就找到了目标值。

如何选择整数型的模板:由check函数判断当check()为真时,是r=mid还是l=mid,由此判断模板类型

版本1(左边界)
当我们将区间[l, r]划分成[l, mid][mid + 1, r]时,其更新操作是r = mid或者l = mid + 1;,计算mid时不需要加1。

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



版本2(右边界)
当我们将区间[l, r]划分成[l, mid - 1][mid, r]时,其更新操作是r = mid - 1或者l = mid;,此时为了防止死循环,计算mid时需要加1。

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

模板3(浮点型)
1.不需要考虑边界
2.while的判断条件是(r - l > INF),INF是答案要求保留的小数在乘1e-2

#include <iostream>
#include <algorithm>

using namespace std;

const double INF = 1e-8;    //INF精度比要求的精度*1e-2

int main()
{
    double x;
    cin >> x;
    double l = -10000, r = 10000; //因为x有可能是负数,所以l和r不能赋值为-x,x
    while(r - l > INF)
    {
        double mid = (l + r) / 2;   //浮点数不支持 >> 运算
        //因为是小数,所以不需要+1或者-1,没有边界问题
        if(mid * mid * mid > x)    r = mid;
        else    l = mid;
    }
    
    printf("%.6f\n", l);
    
    
    return 0;
}

 


例题:

1.double二分

例题:102. 最佳牛围栏 - AcWing题库

思路

1. 答案要求查找一个大于F的区间(即围起来的田地)内牛的平均数的最大值,通过数据容易得出这个值的范围是【1-2000】,满足有序性,所以遍历答案区间【1-2000】,二分查找答案

用check()函数判断答案是否有效,如果有效,L = mid,否则 R = mid

关于二分while的结束条件:因为答案的最大值是2e3,所以说答案需要精确到小数点后三位,但是为了确保答案的一定正确,这里可以精确的1e-5,一般最大精确到1e-7就够了,

2.check(double ans)函数:判断是否有一个范围大于F的区间,且该区间牛的平均值大于ans,显而易见这里需要用到前缀和,但如果直接对牛的数量进行前缀和的话,最后还需要除去这个区间的范围,相当麻烦

所以,这里有一个优化的一个小技巧:对于每一个牛a【i】,让他减去这个平均值ans再进行前缀和,然后就可以直接判断,不需要和区间范围再进行运算

那么如何找到这个区间呢?暴力做法是O(n2)遍历保存没一块地牛的数量的数组a,然后找到一个右边界 j 和一个左边界 l ,使得 r - l > F,且平均值大于ans,这显然是不行的,这里就涉及了另一个优化技巧:在一个for循环内实现遍历操作,新增一个保存最小值的变量minv,用来保存左边界的最小值,因为在不断向右遍历的过程中,左边界的值可以变大也可能变小,所以只需要保存最小的那个边界值的边界 l ,然后从 F 开始遍历右边界 j ,如果在遍历的过程中 a[j] - minv  >= 0,即找到这么一个区间了,那么返回 true,如果一直遍历到右边界还没有找到,返回false

3.答案的输出:因为答案要求向下取整,所以对二分的输出就有要求,那么为什么最后为什么输出int(r1000),而不是int(l1000)呢?这里说一下我的理解:
当我们用实数二分求一个值的时候,这个值会趋近于答案,比如答案是6500,而l等于6499.99999,r等于6500.00001,随着精度的提高,这两边会越来越趋近于6500,这就是实数二分的特点。
因为它趋近于6500,所以我们知道其实答案就是6500,而答案6500向下取整就是6500,所以我们必须用 r 向下取整来得到答案(因为r比答案要大一点点)。
同理,如果题目要求最终答案向上取整,就必须用 l 向上取整来得到最终答案(因为 l 比答案小一点点)。

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 100010;

int a[N];
double sum[N];
int n,f;

bool check(double mid)
{
    for(int i= 1; i <= n; i ++)
        sum[i] = sum[i - 1] + a[i] - mid;
    
    double minv = 0;
    for(int i = 0,j = f; j <= n; j ++,i++)
    {
        minv = min(sum[i], minv);
        if(sum[j] >= minv)
            return true;
    }
    return false;
}

int main()
{
    cin >> n >> f;
    for(int i = 1; i <= n; i ++)
        cin >> a[i];
    
    double l = 1, r = 2000;
    while(r - l > 1e-5)
    {
        double mid = (l + r) / 2;
        if(check(mid))  l = mid;
        else     r = mid;
    }
    
    printf("%d",int(r * 1000));
    
    return 0;
}

参考:AcWing 102. 最佳牛围栏 - AcWing


2.二分插入排序

题目连接:113. 特殊排序 - AcWing题库

// Forward declaration of compare API.
// bool compare(int a, int b);
// return bool means whether a is less than b.

class Solution {
public:
    vector<int> specialSort(int N) {
        vector<int> res;
        res.push_back(1);
        for(int i = 2; i <= N; i ++)
        {
            int l = 0,r = res.size() - 1;
            while(l <= r)
            {
                int mid = l + r >> 1;
                if(compare(i,res[mid])) r = mid - 1;
                else     l = mid + 1;               
            }
            res.push_back(i);
            for(int j = res.size() - 2; j > r; j --)
                swap(res[j], res[j + 1]);
        }
        return res;
    }
};

视频讲解:AcWing 113. 特殊排序 - AcWing


3.二分查找左右边界

题目链接:789. 数的范围 - AcWing题库

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010; 

int a[N], n, q;

int main()
{
    cin >> n >> q;
    for(int i = 0; i < n; i ++ )    cin >> a[i];
    
    while(q -- )
    {
        int k;
        cin >> k;
        
        //查找左边界
        int l = 0, r = n - 1;
        while(l < r)
        {
            int mid = l + r >> 1;
            if(a[mid] > k)  r = mid - 1;
            else if(a[mid] == k) r = mid;
            //上面两步可以合并为if(a[mid] >= k) r = mid;
            else    l = mid + 1;
            
        }
        if(a[l] == k)   cout << l << " ";
        else    cout << -1 << " ";

        //查找右边界
        l = 0, r = n - 1;
        while(l < r)
        {
            int mid = l + r + 1 >> 1;
            if(a[mid] < k)  l = mid + 1;
            else if(a[mid] == k) l = mid;
            //上面两步可以合并为if(a[mid] <= k) l = mid;
            else    r = mid - 1;
        }
        if(a[l] == k)   cout << l << " ";
        else    cout << -1 << " ";
        cout << endl;
        
        
    }
    
    
    
    return 0;
}

posted @ 2022-05-05 08:42  光風霽月  阅读(124)  评论(0编辑  收藏  举报