二分法
专题的思维导图
由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即为所求近似根。