二分模板 Shepard-Wang

二分分为两种,浮点数二分整数二分

浮点数二分相对简单,不需要考虑边界

一 浮点数二分

680. 剪绳子

有N根绳子,第i根绳子长度为Li,现在需要M根等长的绳子,你可以对N根绳子进行任意裁剪(不能拼接),请你帮忙计算出这M根绳子最长的长度是多少。

输入格式

第一行包含2个正整数N、M,表示原始绳子的数量和需求绳子的数量。

第二行包含N个整数,其中第 i 个整数Li表示第 i 根绳子的长度。

输出格式

输出一个数字,表示裁剪后最长的长度,保留两位小数。

数据范围

1≤N,M≤100000 0<Li<10^9

输入样例:

3 4
3 5 4

输出样例:

2.50

样例解释

第一根和第三根分别裁剪出一根2.50长度的绳子,第二根剪成2根2.50长度的绳子,刚好4根。

#include<cstdio>

#define N 100010

int n, m;
int Rope[N];

bool check(double mid)
{
    int cnt = 0;
    for(int i = 0; i < n; ++i)
    {
        // 总数加上 每一根绳子的长度 / mid(绳子的最长长度)
        cnt += Rope[i] / mid;
        if(cnt >= m) return true;
    }
    return false;
}

int main(void)
{
    scanf("%d%d", &n, &m);
    
    for(int i = 0; i < n; ++i)
        scanf("%d", &Rope[i]);
    
    double l = 0, r = 1e9;
    while(r - l > 1e-4)
    {
        double mid = (l + r) / 2;
        // 如果当前的 mid 可以满足分出 m 个绳子,那么绳子可以尝试更长的长度
        if(check(mid)) l = mid;
        else r = mid;
    }
    
    printf("%.2f\n", l);
    
    return 0;
}

注意到 while 循环的判定条件为:while(r - l > 1e-4) 为什么是 1e-4 呢?

对于需要保留 k 位小数的结果,我们尝试到 1e-(2 + k) 即可

还有一种写法是:

int i = 100;
while(i--)
{
    double mid = (l + r) / 2;
    if(check(mid)) l = mid;
    else r = mid;
}

尝试 100 次即可。

时间限制为 1s ,c/cpp 程序一秒大概可以计算 10^7 ~ 10^8 次,我们取较小的 10^7 。check 函数中的 whie 循环的复杂度是 10^5 ,那么 10^7/10^5 = 100 次是较为合理的

790. 数的三次方根

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

输入格式

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

输出格式

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

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

数据范围

−10000≤n≤10000−10000≤n≤10000

输入样例:

1000.00

输出样例:

10.000000
#include<cstdio>
#include<iostream>

double n;

bool check(double mid)
{
    if(mid * mid * mid < n) return true;
    return false;
}

int main(void)
{
    scanf("%lf", &n);
    
    double l, r;
    if(n > 0)
    {
        l = 0;
        r = n;
    }
    else
    {
        l = n;
        r = 0;
    }
    
    while(r - l > 1e-8)
    {
        double mid = (r + l) / 2;
        if(check(mid)) l = mid;
        else r = mid;
    }
    
    printf("%.6f\n", l);
    
    return 0;
}

二 整数二分

整数二分的两种模板

参考

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)

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

1227. 分巧克力

儿童节那天有 K 位小朋友到小明家做客。

小明拿出了珍藏的巧克力招待小朋友们。

小明一共有 N 块巧克力,其中第 i 块是 Hi×Wi 的方格组成的长方形。

为了公平起见,小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。

切出的巧克力需要满足:

  1. 形状是正方形,边长是整数
  2. 大小相同

例如一块 6×5 的巧克力可以切出 6 块 2×2 的巧克力或者 22 块 3×3 的巧克力。

当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?

输入格式

第一行包含两个整数 N 和 K。

以下 N 行每行包含两个整数 Hi 和 Wi。

输入保证每位小朋友至少能获得一块 1×1 的巧克力。

输出格式

输出切出的正方形巧克力最大可能的边长。

数据范围

1≤N,K≤105 1≤Hi,Wi≤105

输入样例:

2 10
6 5
5 6

输出样例:

2
#include<cstdio>

#define N 100010

typedef long long LL;

int n, k;
int H[N], W[N];


bool check(int mid)
{
    LL cnt = 0;
    for(int i = 0; i < n; ++i)
    {
        cnt += (LL)(H[i] / mid) * (W[i] / mid);
        if(cnt >= k) return true;
    }
    return false;
}

int main(void)
{
    scanf("%d%d", &n, &k);
    
    for(int i = 0; i < n; ++i)
    {
        scanf("%d%d", &H[i], &W[i]);
    }
    
    int l = 1, r = (int)1e5;
    while(l < r)
    {
        // 向上取整
        int mid = l + r + 1>> 1;
        if(check(mid)) l = mid;
        else r = mid - 1;
    }
    printf("%d\n", l);
    return 0;
}

注意这道题不能写成:

 while(l < r)
 {
     // 向下取整
     int mid = l + r >> 1;
     if(check(mid)) l = mid + 1;
     else r = mid;
 }

考虑最后的区间 [2,3] 如果 2 是答案的话,这么写会错过最终的答案 2 。退出循环时 l == r == 3

789. 数的范围

给定一个按照升序排列的长度为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<cstdio>

int n, q;
int a[100010];

void Find(int tar)
{
    int l = 0, r = n - 1;
    while(l < r)
    {
        int m = (l + r + 1) >> 1;
        if(a[m] >= tar) r = m - 1;
        else l = m;
    }
    int left = l + 1;
    l = left;
    r = n - 1;
    while(l < r)
    {
        int m = (l + r) >> 1;
        if(a[m] <= tar) l = m + 1;
        else r = m;
    }
    int right = l - 1;
    if(left <= right) 
        printf("%d %d\n", left, right);
    else
        printf("-1 -1\n");
}

int main(void)
{
    scanf("%d%d", &n, &q);
    for(int i = 0; i < n; ++i)
        scanf("%d", &a[i]);
    
    while(q--)
    {
        int tar;
        scanf("%d", &tar);
        Find(tar);
    }
    
    return 0;
}

题解:

记得找到一个临界点满足二分的判断。比如在 [1 2 2 3 3 4] 中找 3 :

我们发现左端点的右侧严格小于 3,右端点的右侧严格大于 3

#include<cstdio>

const int N = 100010;

int n, q;
int a[N];

int main(void)
{
    scanf("%d%d", &n, &q);
    
    for(int i = 0; i < n; ++i)
        scanf("%d", &a[i]);
    
    for(int i = 0; i < q; ++i)
    {
        int m;
        scanf("%d", &m);
        
        // 计算左端点
        int l = 0, r = n - 1;
        while(l < r)
        {
            int mid = l + r >> 1;
            if(a[mid] >= m) r = mid;
            else l = mid + 1;
        }
        int left = l;
        // 如果左端点不是 q 说明 q 不存在
        if(a[l] == m)
        {
            r = n - 1;
            while(l < r)
            {
                int mid = l + r + 1>> 1;
                if(a[mid] <= m) 
                    l = mid; // 如果 mid 给 l,那么计算 mid 时应该加 1
                else r = mid - 1;
            }
            printf("%d %d\n", left, r);
        }
        else
        {
            printf("-1 -1\n");
        }
    }
    
    return 0;
}

22. 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

输入一个升序的数组的一个旋转,输出旋转数组的最小元素。

例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。

数组可能包含重复项。

注意:数组内所含元素非负,若数组大小为0,请返回-1。

样例

输入:nums=[2,2,2,0,1]

输出:0

题解



class Solution {
public:
    int findMin(vector<int>& nums) 
    {
        int n = nums.size() - 1;
        if(n < 0) return -1;
        // 二分时以 nums[0] 为目标点
        // [nums[0], target) 大于等于 nums[0] 
        // [target, nums[n]] 小于 nums[0]
        // 但是这里存在一个问题,可能右区间存在等于 nums[0] 的元素,我们需要去除
        // 比如:[1,2,3,0,1,1]
        while(n > 0 && nums[n] == nums[0]) --n;
        // 上面的过程可能会把右区间全部删除,比如 [1,2,3,1,1]
        if(nums[n] >= nums[0]) return nums[0];
        int l = 0, r = n;
        while(l < r)
        {
            int mid = l + r >> 1;
            if(nums[mid] < nums[0]) r = mid;
            else l = mid + 1;
        }

        return nums[l];
    }
};

113. 特殊排序

有N个元素,编号1.2..N,每一对元素之间的大小关系是确定的,关系具有反对称性,但不具有传递性。

注意:不存在两个元素大小相等的情况。

也就是说,元素的大小关系是N个点与N*(N-1)/2条有向边构成的任意有向图。

然而,这是一道交互式试题,这些关系不能一次性得知,你必须通过不超过10000次提问来获取信息,每次提问只能了解某两个元素之间的关系。

现在请你把这N个元素排成一行,使得每个元素都小于右边与它相邻的元素。

你可以通过我们预设的bool函数compare来获得两个元素之间的大小关系。

例如,编号为a和b的两个元素,如果元素a小于元素b,则compare(a,b)返回true,否则返回false。

将N个元素排好序后,把他们的编号以数组的形式输出,如果答案不唯一,则输出任意一个均可。

数据范围

1≤N≤1000

输入样例

[[0, 1, 0], [0, 0, 0], [1, 1, 0]]

输出样例

[3, 1, 2]

二分 + 插入排序

题解

// 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;
            // 找到的 l 为第一个大于 i 的元素的前一位
            while(l < r){
                int mid = l + r + 1 >> 1;
                if(compare(res[mid],i)) l = mid;
                else    r = mid - 1;
            }
            res.push_back(i);
            for(int j = res.size() - 2; j > r; j--)   swap(res[j],res[j + 1]);
            // 如果所有元素均大于 i ,再交换一次
            if(compare(i, res[l])) swap(res[l], res[l + 1]);
        }
        return res;
    }
};

上一道题的思路和下面一道题有关,我们可以看一下

162. 寻找峰值

峰值元素是指其值大于左右相邻值的元素。

给你一个输入数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞

示例 1:

输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。

示例 2:

输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5 
解释:你的函数可以返回索引 1,其峰值元素为 2;
     或者返回索引 5, 其峰值元素为 6。

提示:

1 <= nums.length <= 1000
-231 <= nums[i] <= 231 - 1
对于所有有效的 i 都有 nums[i] != nums[i + 1]

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/find-peak-element 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。