二分专题:整数二分,浮点二分

专题的思维导图

二分法思维导图

百度百科定义的二分法

由int 而引起的改变

由于整型的取整问题,而导致了

int mid = (l + r) / 2;//下取整
int mid = (l + r + 1) / 2;//上取整

由“可能是答案,肯定不是答案”关键字引起的改变

l = mid; / l = mid + 1; /l = mid - 1;
r = mid; / r = mid +1; / r = mid - 1;

朴素二分

吃瓜群众

题目描述
​ 某地总共有 M 堆瓜,第 i 堆瓜的数量为 Xi。现有 N 组群众现在想要吃瓜,第 i 组群众想要吃的瓜的数量为 Yi。现在对于每组想吃瓜的群众,需要在 M 堆瓜中查找对应数量的一堆瓜,并输出那堆瓜的编号,若找不到对应数量的一堆,则输出 0。

样例输入
5 3
1 3 26 7 15
26 99 3
样例输出
3
0
2

问题分析
1.对输入数据进行排序,找到区间[l , r]= [0, wm[n - 1],使得中间的某个mid 映射的数满足等于n

2.对于这个mid, 比他小的数错,比他大的数错,故满足朴素二分这种情况。所以左右取整无所谓,不妨mid = (l + r) / 2;(mid = (l + r + 1) / 2也行)

3.对于中间的这个数mid,映射为一个数m[mid].num, 如果它满足要求,则它是答案返回它对应的序号m[mid].id;如果mid < n,则mid 在n的左边,mid 不是答案l = mid + 1;如果mid > n,则mid 在 n的右边,则mid 不是答案,r = mid - 1;

/*************************************************************************
	> File Name: ./386.oj.cpp
	> Author: xiaowai
	> Mail: 1871240871@qq.com
	> Created Time: Fri 01 Jan 2021 02:24:41 PM CST
 ************************************************************************/

#include<iostream>
#include<algorithm>
using namespace std;

struct node {
    int id; //序号
    int num;//数字
};

node m[105];
int p[105];

bool cmp(const node& a, const node& b) {
    return a.num < b.num;
}

int binarysearch(node *m, int N, int n) {
    int r = N, l = 1;
    while(l <= r) {
        int mid = (l + r) / 2;
        if(m[mid].num == n) {
            return m[mid].id;
        }
        if(m[mid].num < n) l = mid + 1; 
        else  r = mid - 1;
    }
    return 0;
}

int main() {
    int N, M;
    int i = 0;
    cin >> N >> M;
    for(i = 1; i <= N; i++) {
        cin >> m[i].num;
        m[i].id = i;
    }
    for(i = 1; i <= M; i++) {
        cin >> p[i];
    }
    sort(m + 1, m + N + 1, cmp);
    for(i = 1; i <= M; i++) {
        cout << binarysearch(m, N, p[i]) << endl;
    }
    return 0;
}

01二分

吃瓜群众升级

题目描述
​ 某地总共有 m 堆瓜,第 i 堆瓜的数量为 Xi。现有 n 组群众现在想要吃瓜,第 i 组群众想要吃的瓜的数量为 Yi。现在对于每组想吃瓜的群众,需要在 m 堆瓜中查找大于等于需要数量的第一堆瓜,并输出那堆瓜的编号,若所有瓜堆的数量均小于需要数量,则输出 0。

样例输入
5 5
1 3 26 7 15
27 10 3 4 2
样例输出
0
5
2
4
2

题目分析

1.对输入西瓜数据进行排序,找到区间[l , r]=[0, wm[n]],使得中间的某个mid映射的数等于n.

2.对于这个mid, 比他小的数错,比他大的数对,故满足01二分情况。所以要左取整mid = (l + r) / 2;(因为01左边为0,所以左取整,这里解释比较费劲,简单的说如果右取整是01的右边是1会死循环)

3.对于中间的这个数mid,映射为一个数wm[mid].num, 如果它大于等于5,可能是答案,故r = mid, 如果它小于5,它肯定不是答案,故l = mid - 1;
当l = r时,找到这个数字。
(为什么要定义一个虚拟的瓜堆啊,为了找的数在这个区间内)

/*************************************************************************
	> File Name: ./387.oj.cpp
	> Author: xiaowai
	> Mail: 1871240871@qq.com
	> Created Time: Fri 01 Jan 2021 05:35:49 PM CST
 ************************************************************************/

#include<iostream>
#include<algorithm>
using namespace std;
struct node{
    int id;
    int num;
};
node wm[105];
int p[105];


bool cmp(const node& a, const node& b) {
    return a.num < b.num;
}

int binary_search(node *wm, int N, int n) {
    int l = 0, r = N;
    int mid = (r + l) / 2;
    while(l != r) {
        mid = (r + l) / 2;      
        if(wm[mid].num >= n) {
            r = mid;
        } else {
            l = mid + 1;
        }
    }
    return l;
}

int main() {
    int n, m;
    cin >> n >> m;
    int i = 0;
    for(i = 0; i < n; i++) {
        cin >> wm[i].num;
        wm[i].id = i + 1;
    }
    for(i = 0; i < m; i++) {
        cin >> p[i];
    }
    wm[n].num = 20000000;//定义一个虚拟的瓜堆,确保找的数n在区间内
    wm[n].id = 0;
    sort(wm, wm + n + 1, cmp);
    for(i = 0; i < m; i++) {
        int f = binary_search(wm, n, p[i]);
        cout << wm[f].id << endl;
    }
    return 0;
}

10二分

原木切割

题目描述
​ 某林业局现在 N 根原木,长度分别为 Xi,为了便于运输,需要将他们切割成长度相等的 M 根小段原木(只能切割成整数长度,可以有剩余),小段原木的长度越大越好,现求小段原木的最大长度。例如,有 3 根原木长度分别为 6,15,22,现在需要切成 8 段,那么最大长度为 5。
样例输入
3 8
6
15
22
样例输出
5

题目分析

1.对输入原木数据进行排序,找到区间[l , r]=[0, tree[n - 1]],使得中间的某个mid映射的数大于等于8.

2.对于这个mid, 比他小的数对,比他大的数错,故满足10二分。所以要右取整mid = (l + r + 1) / 2;(因为10右边为0,所以右取整,如果左取整是10的左边是1会死循环)

3.对于中间的这个数mid,映射为一个数f(mid), 如果它大于等于8,可能是答案,故l = mid, 如果它小于8,它肯定不是答案,故r = mid - 1;

/*************************************************************************
	> File Name: ./390.oj.cpp
	> Author: xiaowai
	> Mail: 1871240871@qq.com
	> Created Time: Sat 02 Jan 2021 03:07:11 PM CST
 ************************************************************************/
#include<iostream>
#include<algorithm>
using namespace std;

#define max_n 100
int tree[max_n + 5];

bool cmp(int a, int b) {
    return a < b;
}

int f(int *tree, int len, int n) {
    int ans = 0;
    int i = 0;
    for(i = 0; i < n; i++) {
        ans += tree[i] / len;
    }
    return ans;
}

int bin_search(int *tree, int m, int n) {
    int l = 1, r = n, mid = (l + r + 1) / 2;
    while(r != l) {
        mid = (l + r + 1) / 2;
        if(f(tree, mid, n) >= m) l = mid;
        else r = mid - 1;
    }
    return l;
}

int main() {
    int n, m;
    int i = 0;
    cin >> n >> m;
    for(i = 0; i < n; i++) {
        cin >> tree[i];
    }
    sort(tree, tree + n, cmp);
    cout << bin_search(tree, m, tree[n - 1]) << endl;
    return 0;
}

暴躁的程序猿

题目描述
​ 某公司的程序猿每天都很暴躁,因为他们每个人都认为其他程序猿和自己风格不同,无法一同工作,当他们的工位的编号距离太近时,他们可能会发生语言甚至肢体冲突,为了尽量避免这种情况发生,现在公司打算重新安排工位,因为有些关系户的工位是固定的,现在只有一部分工位空了出来,现在有 n 个程序猿需要分配在 m 个工位中,第 i 个工位的编号为 Xi,工位编号各不相同,现在要求距离最近的两个程序猿之间的距离最大,求这个最大距离是多少。Xi 和 Xj 工位之间距离为|Xi−Xj|。

样例输入
5 3
1
2
8
4
9

样例输出
3
题目分析

1.对输入位置数据进行排序,找到区间[l , r]=[0, seat[n - 1]],使得中间的某个mid映射的数大于等于3个人.

2.对于这个mid, 比他小的数对,比他大的数错,故满足10二分。所以要右取整mid = (l + r + 1) / 2;(因为10右边为0,所以右取整,如果左取整是10的左边是1会死循环)

3.对于中间的这个数mid,映射为一个数f(mid), 如果它大于等于3,可能是答案,故l = mid, 如果它小于3,它肯定不是答案,故r = mid - 1;

/*************************************************************************
	> File Name: ./389.oj.cpp
	> Author: xiaowai
	> Mail: 1871240871@qq.com
	> Created Time: Sat 02 Jan 2021 07:02:39 PM CST
 ************************************************************************/

#include<iostream>
#include<algorithm>
using namespace std;
#define max_n 100
int seat[max_n + 5];

bool cmp(int a, int b) {
    return a < b;
}

int f(int *seat, int n, int d){
    int s = 1, last = seat[0];
    int i = 0;
    for(i = 0; i < n; i++) {
        if(seat[i] - last >= d) {
            s++;
            last = seat[i];
        }
    }
    return s;
}


int bin_search(int max, int m, int n) {
    int l = 1, r = max;
    while(l != r) {
        int mid = (l + r + 1) / 2;
        if(f(seat, n, mid) >= m) l = mid;
        else r = mid - 1;
    }
    return l;
}

int main() {
    int n, m, max;
    int i = 0;
    cin >> n >> m;
    for(i = 0; i < n; i++) {
        cin >> seat[i];
    }    
    sort(seat, seat + n, cmp);
    max = seat[n - 1];
    int max_dist = bin_search(max, m, n);
    cout << max_dist << endl;
    return 0;
}

浮点二分

切绳子

题目描述
​ 有 n 条绳子,它们的长度分别为 Li。如果从它们中切割出 m 条长度相同的绳子,这 m 条绳子每条最长能有多长?答案保留到小数点后 2 位(直接舍掉 2 位后的小数)。

样例输入
4 11
8.02
7.43
4.57
5.39

样例输出
2.00

/*************************************************************************
	> File Name: ./393.oj.cpp
	> Author: xiaowai
	> Mail: 1871240871@qq.com
	> Created Time: Sat 02 Jan 2021 11:02:09 PM CST
 ************************************************************************/

#include<iostream>
#include<algorithm>
using namespace std;

#define max_n 100
#define EPSILON 1e-6

double cord[max_n + 5];

bool cmp(const int& a, const int& b) {
    return a < b;
}

int f(double *cord, double mid, int n) {
    int s = 0;
    int i = 0;
    for(i = 0; i < n; i++) {
        s += cord[i] / mid;
    }
    return s;
}

double bin_search(int max_cord, int m, int n) {
    double l = 0.0, r = max_cord;
    while(r - l > EPSILON) {
        double mid = (l + r) / 2;
        if(f(cord, mid, n) >= m) l = mid;
        else r = mid;
    }
    return l;
}
int main() {
    int n, m;
    double max_cord, max_length;
    cin >> n >> m;
    int i = 0;
    for(i = 0; i < n; i++) {
        cin >> cord[i];
    }
    sort(cord, cord + n, cmp);
    max_cord = cord[n - 1];
    max_length = bin_search(max_cord, m, n); 
    cout.setf(ios_base::fixed, ios_base::floatfield);
    cout.precision(2);
    cout << max_length << endl;
    return 0;
}

整型二分技巧总结

1.对输入数据进行排序,找到区间【l , r】,使得中间的mid 映射的数满足要求

2.对于这个mid, 比他小的数对(错),比他大的数错(对),故满足10(01)这种情况。所以要右(左)取整mid = (l + r + 1) / 2;

3.对于中间的这个数mid,映射为一个数f(mid), 如果它满足要求,它可能是答案,故1的相对位置(靠左还是靠右) = mid, 如果它不满足要求,它肯定不是答案,故0的相对位置 = mid - 1;
(如果你缩减区间left, right 分不清,想象11110000,mid可能是答案,1靠左,所以left缩减,l = mid;如果mid 肯定不是答案,0靠右,所以right缩减,r = mid - 1;)

浮点二分技巧总结

正常的数学思想,别和整型二分搞混就可以。

附上纯数学二分法运算步骤

步骤1: 求边界: 计算f(x)在有根区间[a,b]端点处的值f(a),f(b).
步骤2: 求中点 计算f(x)在区间中点 (a+b)/2处的值 f((a+b)/2).
步骤3: 判断 若f((a+b)/2)=0,则(a+b)/2即是根,计算过程结束,否则判断,若f((a+b)/2)f(a)<0,则根在[a, (a + b) / 2)] 区间内,否则根在[ (a + b) / 2, b) 区间内.

对新的区间反复执行上述步骤,直到区间[a,b]的长度小于允许误差e,此时中点(a+b)/2即为所求近似根。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值