1. 整数二分
核心是边界问题
第一个模板是尽量往左找目标,第二个模板是尽量往右找目标。
只要是往右找答案,ans在红色的右端点,就用第一个模板,mid要加一,l=mid,r要减一;
只要是往左找答案,ans在绿色的左端点,就用第二个模板,mid不用加一,r=mid,l加一。
模板1
如果是l = mid
,则上一步的mid向上取整,需要加1:int mid = l + r + 1 >> 1;
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[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;
}
模板2
如果是r = mid
,则上一步的mid向下取整int mid = l + r >> 1;
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
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;
}
例题
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
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;
//找左边界,即第一个≥x的数
while (l < r)
{
int mid = l + r >> 1;
if (q[mid] >= x) r = mid;
else l = mid + 1;
}
//因为在找第一个≥x的数,当找到最后没有的时候,说明不存在
if (q[l] != x) cout << "-1 -1" << endl;
else //如果存在就继续找右边界,即第一个≤x的数
{
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; //最后l和r是重合的,所以输出哪个都可以
}
}
return 0;
}
2. 浮点数二分
浮点数就没有整数的加1减1问题。
模板
经验:精度比题目要求的小数点位数多2。如保留6位小数,设eps = 1e-8
。
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;
}
例题
数的三次方
注意l
r
取值问题
#include <iostream>
using namespace std;
int main()
{
double x;
cin >> x;
//注意这里的r不能取到x,如果x是0~1的,如0.01,0.01开根号是0.1,就不在0~x的范围内了
//因此r是不能小于1的 -> 错误写法double l = 0, r = 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("%.6lf\n", l);
return 0;
}