目录
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;
}
// 区间[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、边界问题
在 bsearch_2( ) 中,若 int mid = l + r >> 1 ,当 r = l + 1 时,mid 向下取整为 l ,此时假如check() 返回 true,执行 l = mid( mid == l ),那么区间就没有变 ( 原来是 [l,r],更新后还是 [l,r] ),则陷入死循环。所以改为 int mid = l + r + 1 >> 1,这样区间从 [l,r] 变为 [r,r]。
2、未找到元素
题目可能是无解的,但二分模板一定保证有解。函数返回的是从左往右看第一个 >=x 的数;如果序列中不存在 x,返回的是从左往右看第一个 >x 的数。这样保证函数在查不到目标值时也是正常运行的,不会陷入死循环。我们只需接受函数的返回值并判断是否与目标值相等,不等则表明未找到元素。
写题时步骤:
1、先把 check( ) 函数 和 int mid = l + r >> 1; 写好
bool check(int x) {/* ... */} // 检查x是否满足某种性质
int bsearch(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid))
else
}
return l;
}
2、根据 check() 函数中的条件想下该怎么划分区间,r=mid 还是 l=mid
3、如果是 l=mid ,那么 int mid = l + r + 1 >> 1;
2、浮点数二分
浮点数二分没有边界问题
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;
}
【经验】
如果题目要求保留4位小数,eps就取1e-6;
如果题目要求保留6位小数,eps就取1e-8;
作为条件判断的精度数至少要比题目要求的有效位多2位