二分

二分

二分的本质不是单调性。有单调性的题目一定可以二分,可以二分的题目不一定必须有单调性。

1. 整数二分

1.1 主要思想

在一个区间内部,使用二分的方法得到答案。每次选择答案所在的区间进行二分,且每次都能保证区间内有答案。对于有解的问题,二分得到的是正确答案;对于无解的题目,二分得到的是答案应该存在的位置。

1.2 整数二分的实现过程

  1. 找一个中间值,即mid = (l + r) / 2
  2. (假设判断的是红色的性质)每次判断一下中间值是否满足性质,即if(check(mid))
    • 情况一:true
      • 说明mid满足该性质,因此ans所在区间为[mid, r]
      • 更新方式:l = mid
    • 情况二:false
      • 说明mid不满足该性质,因此ans所在区间为[l, mid - 1]
      • 更新方式:r = mid - 1
  3. (假设判断的是绿色的性质)每次判断一下中间值是否满足性质,即if(check(mid))
    • 情况一:true
      • 说明mid满足该性质,因此ans所在区间为[l, mid]
      • 更新方式:r = mid
    • 情况二:false
      • 说明mid不满足该性质,因此ans所在区间为[mid + 1, r]
      • 更新方式:l = mid + 1

中间值“是否+1”的问题

如果mid = (l + r) / 2,当l = r - 1时,求得mid的值为l,倘若此时check()正好取得true,则会执行l = mid,即l更新后的值还是l,就会陷入死循环状态。

1.3 模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}

2. 浮点数二分

2.1 浮点数二分的实现过程

由于浮点数除法的结果仍然是浮点数,是保留精度的,其与整数除法的整除存在一定的差异。因此,浮点数二分不用考虑边界问题,其能够准确的把两个区间分为两部分。

2.2 模板

1
2
3
4
5
6
7
8
9
10
11
12
13
bool check(double x) {/* ... */} // 检查x是否满足某种性质

double bsearch_3(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps) // 注意是大-小, 或者直接用abs()函数取绝对值
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}

经验:如果题目要求保留4位小数,则r-l应该小于1e-6;如果题目要求保留5位小数,则r-l应该小于1e-7;如果题目要求保留6位小数,则r-l应该小于1e-8


二分
http://ihrd.github.io/2023/07/01/二分/
作者
Zehao Zhang
发布于
2023年7月1日
许可协议