写正确的整数二分 |oyxBlog

二分

第一篇二分搜索论文是 1946 年发表,然而第一个没有 bug 的二分查找法却是在 1962 年才出现,中间用了 16 年的时间。
——不知道哪里看的

整数二分

yxc二分模板
二分的本质是二段性不是单调性。


当想找不满足性质的边界值(红色区域的右边界值)

  1. 找中间值 mid = (l+r+1)/2
  2. if(check(mid))等于true或者是false
    check(m)是检查m是在不满足性质的区间(检查是不是在红色区间)
  3. 更新l或者r

二分左区间的右端点


当想找满足性质的边界值(绿色区域的左边界值)

  1. 找中间值 mid = (l+r)/2
  2. if(check(mid))等于true或者是false
    check(m)是检查m是在满足性质的区间(检查是不是在绿色区间)
  3. 更新l或者r

二分右区间的左端点


归结上面的两种二分方法,步骤为:

  1. 先写一个check函数
  2. 判定在check的情况下(true和false的情况下),如何更新区间。
  3. 在check(m)==true的分支下是:
    1. l=mid的情况,中间点的更新方式是m=(l+r+1)/2
    2. r=mid的情况,中间点的更新方式是m=(l+r)/2

这种方法保证了:

  1. 最后的l==r
  2. 搜索到达的答案是闭区间的,即a[l]是满足check()条件的。

模板

acwing 789

//#define judge
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
int n, q, k;
int a[maxn];
int main() {
#ifndef judge
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  scanf("%d%d", &n, &q);
  for (int i = 0; i < n; i++) {
    scanf("%d", &a[i]);
  }
  for (int i = 0; i < q; i++) {
    scanf("%d", &k);
    int l = 0, r = n - 1;
    while (l < r) {
      int mid = l + r >> 1;
      if (a[mid] >= k) {
        r = mid;  //第二种情况 找绿色区间的左端点
      } else {
        l = mid + 1;
      }
    }
    if (a[l] != k) {
      printf("-1 -1
");
    } else {
      printf("%d ", l);
      l = 0, r = n - 1;
      while (l < r) {
        int mid = l + r + 1 >> 1;
        if (a[mid] <= k) {//第一种情况 找红色区间的右端点
          l = mid;
        } else {
          r = mid - 1;
        }
      }
      printf("%d
",l);
    }
  }
  return 0;
}

一些其他细节

为什么需要+1?
原因是如果不加上1,那么mid得到的是下取整的数,那么有可能[m,r]更新过后m会一直等于m(m+1==r的情况)会陷入死循环。

原文地址:https://www.cnblogs.com/adameta/p/12269341.html