二分的基础用法是在单调序列或单调函数中进行查找。因此当问题的答案具有单调性时,就可以通过二分把求解转化为判定。其实,写二分的时候并不容易,写check函数的时候,有很多细节之处需要仔细考虑。

  • 对于整数域上的二分,需要注意终止边界、左右区分取舍时的开闭请,避免漏掉进入死循环。
  • 对于实数域上的二分,需要注意精度问题。

二分的实现模板有很多种,以下给出的模板是yxc的,非常好用。

  1. //模板请记忆
  2. //整数二分
  3. bool check(int x) {/* ... */} // 检查x是否满足某种性质
  4. // 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
  5. int bsearch_1(int l, int r)
  6. {
  7. while (l < r)
  8. {
  9. int mid = l + r >> 1;
  10. if (check(mid)) r = mid; // check()判断mid是否满足性质
  11. else l = mid + 1;
  12. }
  13. return l;
  14. }
  15. // 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
  16. int bsearch_2(int l, int r)
  17. {
  18. while (l < r)
  19. {
  20. int mid = l + r + 1 >> 1;
  21. if (check(mid)) l = mid;
  22. else r = mid - 1;
  23. }
  24. return l;
  25. }
  26. //注意,分析题意,画数轴,看mid的右边是答案,还是mid的左边是答案

upd20211115,对于二分模板的认识,我现在认为不应该局限于模板本身,下面的代码更好去理解,也很专业。在初赛真题当中,有很多次二分,具体代码实现的环节,就有两种

  1. while (l < r)
  2. while (l <= r)
  3. 这两种在当时判断边界是用填空去写的,比较容易进坑,需要代数模拟。现在这两种写法一起组合,对二分的理解用会深入很多。边界确实是一个头疼的话题,很容错,然后,很难调。
  4. 我现在更青睐后者
  1. // 数的范围
  2. // https://www.acwing.com/problem/content/description/791/
  3. #include <bits/stdc++.h>
  4. using namespace std;
  5. const int N = 1e5 + 10;
  6. int n, q, a[N], k;
  7. int solve(int k){
  8. int l = 0, r = n - 1, best = -1;
  9. while (l <= r){
  10. int mid = (l + r) >> 1;
  11. if (a[mid] <= k){
  12. best = mid;
  13. l = mid + 1;
  14. }
  15. else
  16. r = mid - 1;
  17. }
  18. if (a[best] != k) return -1;
  19. return best;
  20. }
  21. int solve2(int k){
  22. int l = 0, r = n - 1, best = -1;
  23. while (l <= r){
  24. int mid = (l + r) >> 1;
  25. if (a[mid] >= k){
  26. best = mid;
  27. r = mid - 1;
  28. }
  29. else
  30. l = mid + 1;
  31. }
  32. if (a[best] != k) return -1;
  33. return best;
  34. }
  35. int main(){
  36. cin >> n >> q;
  37. for (int i = 0; i < n; i++) scanf("%d", &a[i]);
  38. while (q--){
  39. scanf("%d", &k);
  40. int l = solve2(k);
  41. int r = solve(k);
  42. printf("%d %d\n", l, r);
  43. }
  44. return 0;
  45. }
  1. //模板请记忆
  2. //浮点数二分,还有循环50次的写法
  3. bool check(double x) {/* ... */} // 检查x是否满足某种性质
  4. double bsearch_3(double l, double r)
  5. {
  6. const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
  7. while (r - l > eps)
  8. {
  9. double mid = (l + r) / 2;
  10. if (check(mid)) r = mid;
  11. else l = mid;
  12. }
  13. return l;
  14. }
  15. double bsearch_4(double l, double r)
  16. {
  17. int T = 50;
  18. while (T--)
  19. {
  20. double mid = (l + r) / 2;
  21. if (check(mid)) r = mid;
  22. else l = mid;
  23. }
  24. return l;
  25. }

例题,和为给定数

  1. //

例题,查找最接近的元素

  1. //