AcWing 789. 数的范围

题目传送门

一、算法模板

二分模板一共有两个,分别适用于不同情况。

算法思路:假设目标值在闭区间\([l, r]\)中, 每次将区间长度缩小一半,当\(l = r\)时,我们就找到了目标值。

1、向左逼近

当我们将区间\([l, r]\)划分成\([l, mid]\)\([mid + 1, r]\)时,其更新操作是\(r = mid\)或者\(l = mid + 1\),计算\(mid\)时不需要加\(1\)

int bsearch_1(int l, int r){
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}

2、向右逼近

当我们将区间\([l, r]\)划分成\([l, mid - 1]\)\([mid, r]\)时,其更新操作是\(r = mid - 1\)或者\(l = mid\),此时为了防止死循环,计算\(mid\)时需要加1。

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;
}

总结:

\(mid\)划到左侧,则\(mid=(l+r)>>1\)

\(mid\)划到右侧,则\(mid=(l+r+1)>>1\)

栗子1

\(\{2,3,4,4,4,5,6,7\}\)中找到左侧数第一个\(4\)

  • 第一步:先把\(mid=(l+r)>>1\)写上。

  • 第二步: 思考\(check(mid)\)函数,写为if(q[mid]>=x) return true;。因为只有大于等于,才能向逼近。

  • 第三步:大于等于目标值,\(mid\)是可能的解,所以\(r=mid\),如果不是,表示小于目标值,需要向右找:\(l=mid+1\)

  • 第四步:向左逼近,第一步的\(mid\)值是不需要改的。

栗子2

\(\{2,3,4,4,4,5,6,7\}\)中找到右侧数第一个\(4\)

  • 第一步:先把\(mid=(l+r)>>1\)写上。

  • 第二步: 思考\(check(mid)\)函数,写为if(q[mid]<=x) return true;。因为只有小于等于,才能向逼近。

  • 第三步:小于等于目标值,\(mid\)是可能的解,所以\(l=mid\),如果不是,表示大于目标值,需要向右找:\(r=mid-1\)

  • 第四步:向右逼近,第一步的\(mid\)值需要\(+1\)

二、实现代码

#include <bits/stdc++.h>

using namespace std;
const int N = 100010;
int n, m;
int q[N];//数组本身是有序的
int l, r;

int main() {
    //读入优化
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 0; i < n; i++) cin >> q[i];
    //m次询问
    while (m--) {
        int x;
        cin >> x;
        //本题要求数组下标从0开始
        l = 0, r = n - 1;
        //寻找左边界
        while (l < r) {
            int mid = (l + r) >> 1;
            if (q[mid] >= x) r = mid;//如果q[mid]>=x,那么首次出现的x应该在左侧,同时,mid位置可能是解
            else l = mid + 1;//如果q[mid]<x,那么解一定在右侧,并且mid肯定不是解。
        }
        //如果没有找到的话
        if (q[l] != x) {
            cout << "-1 -1" << endl;
            continue;
        }
        //输出左边界
        cout << l << ' ';

        //寻找右边界
        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;
    }
    return 0;
}

三、总结思考

为什么找左边界是q[mid]>=x,而找右边界是q[mid]<=x呢?

为了找最左边界,需要判断条件是大于等于,这样,中位值大于等于目标值,才会继续向左逼近。

为了找最右边界,需要判断条件是小于等于,这样,中位值小于等于目标值,才会继续向右逼近。

四、示例

#include <bits/stdc++.h>
using namespace std;
int n, m;
const int N = 110;

int l, r;
int main() {
  //从0~5找出>=3的第一个位置
  int q1[N] = {0, 1, 2, 4, 5, 7};
  l = 0, r = 5;
  int x = 3;
  while (l < r) {
    int mid = (l + r) >> 1;
    if (q1[mid] >= x)
      r = mid;
    else
      l = mid + 1;
  }
  cout << l << endl;

  //从0~6找出<=3的最后一个位置
  int q2[N] = {1, 2, 3, 3, 3, 4, 5};
  l = 0, r = 6;
  x = 3;
  while (l < r) {
    int mid = (l + r + 1) >> 1;
    if (q2[mid] <= x)
      l = mid;
    else
      r = mid - 1;
  }
  cout << l << endl;
  return 0;
}

原文地址:https://www.cnblogs.com/littlehb/p/15232810.html