Post

AcWing789.数的范围做题笔记——二分

AcWing789.数的范围做题笔记——二分

原题

给定一个按照升序排列的长度为n的整数数组,以及q个查询。

对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。

如果数组中不存在该元素,则返回 -1 -1

输入格式 第一行包含整数 n 和 q ,表示数组长度和询问个数。

第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。

接下来 q 行,每行包含一个整数 k ,表示一个询问元素。

输出格式 共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回 -1 -1

数据范围 1≤n≤100000

1≤q≤10000

1≤k≤10000

输入样例:

1
2
3
4
5
6 3
1 2 2 3 3 4
3
4
5

输出样例:

1
2
3
3 4
5 5
-1 -1

题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, q, k;
int arr[N];
int main(){
    int n;
    cin >> n >> q;
    for(int i = 0; i < n; i ++){
        cin >> arr[i];
    }
    for(int i = 0; i < q; i ++){
        cin >> k;
        int l = 0, r = n - 1;
        while(l < r){
            int mid = l + r >> 1;
            if(arr[mid] >= k) r = mid;
            else l = mid + 1;
        }
        if(arr[l] != k) cout << "-1 -1" << endl;
        else{
            cout << l << ' ';
            l = 0, r = n - 1;
            while(l < r){
                int mid = l + r + 1 >> 1;
                if(arr[mid] <= k) l = mid;
                else r = mid - 1;
            }
            cout << l << endl;
        }
    }
    
    return 0;
}

关键点

第一次二分:查找起始位置(第一个 ≥ x 的位置)

1
2
3
4
5
6
7
int l = 0, r = n - 1;
while (l < r) {
    int mid = l + r >> 1;  // 向下取整
    if (q[mid] >= x) r = mid;
    else l = mid + 1;
}
// 结束时 l == r,即起始位置

收缩方向

当 q[mid] >= x 时,r=mid:向左收缩(因为左边可能有更小的 ≥x 的值,直到探出左边界)

当 q[mid] < x 时,l=mid+1:向右跳跃(当前值太小,必须跳过)

边界情况

若所有元素 < x:l 停在 n-1(需后续检查)

若所有元素 ≥ x:l 停在 0(最小位置)

第二次二分:查找结束位置(最后一个 ≤ x 的位置)

1
2
3
4
5
6
7
8
int left = l;  // 从起始位置开始
int right = n - 1;
while (left < right) {
    int mid = left + right + 1 >> 1;  // 向上取整
    if (q[mid] <= x) left = mid;
    else right = mid - 1;
}
// 结束时 left == right,即结束位置

收缩方向

当 q[mid] <= x 时,left=mid:向右试探(可能还有更大的 ≤x 的值,直到探出右边界)

当 q[mid] > x 时,right=mid-1:向左收缩(当前值太大,必须跳过)

向上取整

mid = (left + right + 1)/2 防止死循环

当区间长度为2时(如 left=1, right=2):

向下取整:mid=1 → 若执行 left=mid 则区间不变

向上取整:mid=2 → 可安全收缩区间

This post is licensed under CC BY 4.0 by the author.