————出自南昌理工学院ACM集训队
整数二分基本思想
二分,是从一系列有序对中找出某一个符合条件的数,找到之后把这个数的位置给返回,
基本思想就是,我给你一个数据范围,打个比方1—200,我让你猜我心中所想的那个数175,最基本的方法就是从头到尾开始猜,是1吗,不是,是2吗,不是。。。,一直猜到答案为止,这样猜的话,最坏的情况是要猜200次。
不过用二分来找就更为巧妙一点,你看,要从1—200内猜一个数,那我们可以直接先取1—200的一半,就直接从100开始猜,我会告诉你小了,小了就直接再猜100—200的一半150,我又会告诉你小了,你就再可以从150—200中,取中间值175,这样就猜对了。
现在让我们来归纳一下二分基本思想:二分就是每次取数据范围的一半,然后判断一半这个点是否符合条件,然后来选择是取这个点的上半部分还是下半部分,之后再取选择完后的范围再取中间点,以此循环,直到找到符合条件的答案为止。
模板:
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
//查找左边界时
int bsearch_1(int l, int r)
{
while (l < r)//循环到最后l和r相差1就为答案
{
int mid = l + r >> 1;//范围的中间值 >>1比/2稍微要快一点
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
//查找右边界时
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;//如果mid=l+r>>1的话会陷入死循环
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
//作者:yxc
//链接:https://www.acwing.com/blog/content/277/
例题
给定一个按照升序排列的长度为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 <iostream>
#include <stdio.h>
#include <algorithm>
#include <cstring>
typedef long long ll;
using namespace std;
const int N = 1e5 + 5;
int main()
{
int n, m,a[N];
scanf("%d %d", &n, &m);
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
while (m--) {
int x;
scanf("%d", &x);
int l = 0, r = n - 1;//定义边界
while (l < r) {//找的是左边界也就是要查询数的初始位置
int mid = l + r >> 1;//取中间的点
if (a[mid] >= x) r = mid;//a[mid]大于x,就将区间缩小为[l,mid]
else l = mid + 1;//小了就将区间更新为[mid+1,r],因为是整数二分,所以是mid+1
}
if (a[l] != x) {//找不到就输出-1 -1
cout << "-1 -1" << endl;
}
else {
cout << l << " ";
int l = 0, r = n - 1;
while (l < r) {//找要查询数的终止位置
int mid = l + r + 1 >> 1;
//查找的是右边界,所以右边界的左边都是<=a[mid]的
if (a[mid] <= x) l = mid;//a[mid]小了就将区间更新为[mid,r]
else r = mid - 1;//大了就更新为[mid-,r]
}
cout << l << endl;
}
}
return 0;
}
浮点数二分基本思想
浮点数二分和整数二分不同,浮点数不存在由于(整数)取整导致的边界问题,每次二分区间严格减半, 因此比整数二分简单的多,每次更新边界时直接让r = mid或l = mid即可。
模板
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;
}
//作者:yxc
//链接:https://www.acwing.com/blog/content/277/
让我们来看个例题
例题
给定一个浮点数n,求它的三次方根。
输入格式
共一行,包含一个浮点数n。
输出格式
共一行,包含一个浮点数,表示问题的解。注意,结果保留6位小数。
数据范围
−10000≤n≤10000
输入
1000.00
输出
10.000000
代码:
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <cstring>
typedef long long ll;
using namespace std;
const int N = 1e5 + 5;
int main()
{
double n;
cin >> n;
if (n < 0) {//如果为负数时,先转换一下
n = -n;
cout << "-";
}
double l = 0, r = n;
while (r - l > 1e-8) {//一般取题目要求位数再加两位,题目为10^-6,所以我们取10^-8
double mid = (l + r) / 2;
if (mid * mid * mid >= n) r = mid;//边界都是直接更新一半
else l = mid;
}
printf("%lf", l);//保留六位小数
return 0;
}