|
|
|
|
## [$AcWing$ $789$. 数的范围](https://www.acwing.com/problem/content/791/)
|
|
|
|
|
|
|
|
|
|
### 一、题目描述
|
|
|
|
|
给定一个按照升序排列的长度为 $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$
|
|
|
|
|
|
|
|
|
|
**输入样例:**
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
6 3
|
|
|
|
|
1 2 2 3 3 4
|
|
|
|
|
3
|
|
|
|
|
4
|
|
|
|
|
5
|
|
|
|
|
```
|
|
|
|
|
**输出样例:**
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
3 4
|
|
|
|
|
5 5
|
|
|
|
|
-1 -1
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<font color='red' size=4><b>命名规则</b></font>
|
|
|
|
|
为描述方便,下面文中提示的:
|
|
|
|
|
|
|
|
|
|
升序理解为不降序,即按顺序输入的数字,每个数字对比前面的数字,可等于可大于,但不能小于。
|
|
|
|
|
|
|
|
|
|
降序理解为不升序,即按顺序输入的数字,每个数字对比前面的数字,可等于可小于,但不能大于。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 二、函数定义与用途
|
|
|
|
|
$\large lower\_bound$
|
|
|
|
|
|
|
|
|
|
**用途**:
|
|
|
|
|
<font color='blue' size=4><b>升序</b></font>的情况下,$lower\_bound$返回第一个 <font color='red' size=4><b>大于等于$val$</b></font>的位置。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<font color='blue' size=4><b>降序</b></font>的情况下,$lower\_bound$返回第一个 <font color='red' size=4><b>小于等于$val$</b></font>的位置。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$\large upper\_bound$
|
|
|
|
|
**用途**:
|
|
|
|
|
<font color='blue' size=4><b>升序</b></font>的情况下,$upper\_bound$返回第一个 <font color='red' size=4><b>大于$val$</b></font>的位置。
|
|
|
|
|
|
|
|
|
|
<font color='blue' size=4><b>降序</b></font>的情况下,$upper\_bound$返回第一个 <font color='red' size=4><b>小于$val$</b></font>的位置。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 三、$STL$内置方法
|
|
|
|
|
|
|
|
|
|
$\large lower\_bound$
|
|
|
|
|
|
|
|
|
|
<font color='blue' size=4><b>升序</b></font>
|
|
|
|
|
```c++
|
|
|
|
|
int p = lower_bound(q, q + n, x) - q;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<font color='blue' size=4><b>降序</b></font>
|
|
|
|
|
```c++
|
|
|
|
|
int p = lower_bound(q, q + n, x, greater<int>()) - q;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
$\large upper\_bound$
|
|
|
|
|
|
|
|
|
|
<font color='blue' size=4><b>升序</b></font>
|
|
|
|
|
```c++
|
|
|
|
|
int p = upper_bound(q, q + n, x) - q;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<font color='blue' size=4><b>降序</b></font>
|
|
|
|
|
```c++
|
|
|
|
|
int p = upper_bound(q, q + n, x, greater<int>()) - q;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 四、手写左闭右开(推荐写法)
|
|
|
|
|
|
|
|
|
|
<font color='blue' size=4><b>升序</b></font>
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
int lower_bound(int q[], int l, int r, int x) {
|
|
|
|
|
while (l < r) {
|
|
|
|
|
int mid = (l + r) / 2;
|
|
|
|
|
if (q[mid] >= x)
|
|
|
|
|
r = mid;
|
|
|
|
|
else
|
|
|
|
|
l = mid + 1;
|
|
|
|
|
}
|
|
|
|
|
return l;
|
|
|
|
|
}
|
|
|
|
|
int upper_bound(int q[], int l, int r, int x) {
|
|
|
|
|
while (l < r) {
|
|
|
|
|
int mid = (l + r) / 2;
|
|
|
|
|
if (q[mid] > x)
|
|
|
|
|
r = mid;
|
|
|
|
|
else
|
|
|
|
|
l = mid + 1;
|
|
|
|
|
}
|
|
|
|
|
return l;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
<font color='blue' size=4><b> 降序</b></font>
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
int lower_bound(int q[], int l, int r, int x) {
|
|
|
|
|
while (l < r) {
|
|
|
|
|
int mid = (l + r) / 2;
|
|
|
|
|
if (q[mid] <= x)
|
|
|
|
|
r = mid;
|
|
|
|
|
else
|
|
|
|
|
l = mid + 1;
|
|
|
|
|
}
|
|
|
|
|
return l;
|
|
|
|
|
}
|
|
|
|
|
int upper_bound(int q[], int l, int r, int x) {
|
|
|
|
|
while (l < r) {
|
|
|
|
|
int mid = (l + r) / 2;
|
|
|
|
|
if (q[mid] < x)
|
|
|
|
|
r = mid;
|
|
|
|
|
else
|
|
|
|
|
l = mid + 1;
|
|
|
|
|
}
|
|
|
|
|
return l;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 五、经验总结
|
|
|
|
|
|
|
|
|
|
* 二分模板在网上非常多,在边界处理上有各种各样的处理方式,感受后,认为$STL$的思路是最好的:**左闭右开**
|
|
|
|
|
|
|
|
|
|
* 一般来讲,$STL$可以处理大于等于,大于,小于等于,小于,一般的数字二分够用
|
|
|
|
|
|
|
|
|
|
* 但是,由于二分不一定是数字二分,有时需要用$check$函数,这里$STL$就无法使用$check$函数了,所以,终极解法还是手写二分,容易扩展!
|
|
|
|
|
|
|
|
|
|
* 查找左边界,可以直接$lower\_bound$,如果想要查找右边界,可以使用$upper\_bound$然后再减$1$。
|
|
|
|
|
|
|
|
|
|
### 六、实现代码
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
#include <bits/stdc++.h>
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
|
|
const int N = 100010;
|
|
|
|
|
int n;
|
|
|
|
|
int q[N];
|
|
|
|
|
// 左闭右开 [ )
|
|
|
|
|
int lower_bound(int l, int r, int x) {
|
|
|
|
|
while (l < r) {
|
|
|
|
|
int mid = (l + r) >> 1;
|
|
|
|
|
if (q[mid] >= x)
|
|
|
|
|
r = mid;
|
|
|
|
|
else
|
|
|
|
|
l = mid + 1;
|
|
|
|
|
}
|
|
|
|
|
return l;
|
|
|
|
|
}
|
|
|
|
|
int upper_bound(int l, int r, int x) {
|
|
|
|
|
while (l < r) {
|
|
|
|
|
int mid = (l + r) >> 1;
|
|
|
|
|
if (q[mid] > x)
|
|
|
|
|
r = mid;
|
|
|
|
|
else
|
|
|
|
|
l = mid + 1;
|
|
|
|
|
}
|
|
|
|
|
return l;
|
|
|
|
|
}
|
|
|
|
|
int main() {
|
|
|
|
|
int T;
|
|
|
|
|
cin >> n >> T;
|
|
|
|
|
for (int i = 0; i < n; i++) cin >> q[i];
|
|
|
|
|
|
|
|
|
|
while (T--) {
|
|
|
|
|
int x;
|
|
|
|
|
cin >> x;
|
|
|
|
|
int p = lower_bound(0, n, x); //[0,n)
|
|
|
|
|
if (q[p] != x) {
|
|
|
|
|
puts("-1 -1");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
printf("%d ", p);
|
|
|
|
|
p = upper_bound(0, n, x);
|
|
|
|
|
printf("%d\n", p - 1);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
```
|