对于二分算法来说,二分是一定会找到答案的。因为二分不断压缩区间,最后必定会分出一个值。
但是这个答案仅仅是对于二分这个算法求出的答案。答案是否正确,还是要结合题目判断。简约的说就是二分的答案不一定是题目的答案,但是二分一定会有答案,无解是对于问题的。
整数二分
算法模板
模板1:区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
bool check(int x) {/* ... */} // 检查x是否满足某种性质
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
模板2:区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
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;
}
总结:模板 1 找 区间左边界点,模板 2 找 区间右边界点。
例如模板1在上图中找的就是 第一个 3,左端点 ;模板2找的是 最后一个3,右端点 。
模板题 数的范围
题目:
给定一个按照升序排列的长度为 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
想要找到目标值 x 的起始坐标,可理解成找到 ≥x 的最小值,再判断找到的边界值是否与 x 相等,若不相等返回 -1;同样,想要找到目标值 x 的终止坐标,可理解成找到 ≤x 的最大值,再判断找到的边界值是否与 x 相等,若不相等返回 -1;
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int q[N];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);
while (m -- )
{
int x;
scanf("%d", &x);
int l = 0, r = n - 1;
while (l < r)
{ //找左端点
int mid = l + r >> 1;
if (q[mid] >= x) r = mid;
else l = mid + 1;
}
//二分必定会找到答案,但答案可能不符合题意
//这里二分得出的答案不等于x就是错误的,直接输出-1返回
if (q[l] != x) cout << "-1 -1" << endl;
else
{
cout << l << ' ';
int l = 0, r = n - 1;
while (l < r)
{ //找右端点
int mid = l + r + 1 >> 1;
if (q[mid] <= x) l = mid;
else r = mid - 1;
}
cout << l << endl;
}
}
return 0;
}
浮点数二分
浮点数二分没有整数,每次区间长度一定可以严格缩小一半,不需要处理边界,一定要时时刻刻保证答案在区间内部,一开始可能在 l ~ r,每次通过中间点判断答案落在哪一半边,只要保证每一次答案落在区间中就可以了,当区间长度很小的时候,就可以认为我们找到了答案
比如当整个浮点数二分区间的长度 r - l 已经 ≤ 10^-6(就可以把它当做一个数了),可以用 l 或者 r 当做答案
算法模板
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;
}
不用精度来表示迭代,直接循环 100 次( 相当于把整个区间的长度 / 2^100 )也可以
循环 n 次,相当于把整个区间的长度 / 2^n
模板题 数的三次方根
给定一个浮点数n,求它的三次方根。
输入格式
共一行,包含一个浮点数n。
输出格式
共一行,包含一个浮点数,表示问题的解。
注意,结果保留6位小数。
数据范围
−10000≤n≤10000
输入样例:
1000.00
输出样例:
10.000000
经验值:
当结果保留6位小数时,r-l = 1e-8;
当结果保留5位小数时,r-l = 1e-7;
往后多开两位
#include <iostream>
using namespace std;
int main()
{
double x;
cin>>x;
double l = -10000, r = 10000;
while(r - l >=1e-8)
{
double mid = (l + r )/2;
if(mid*mid*mid>=x) r = mid;
else l = mid;
}
printf("%lf",l);
return 0;
}