引用
引用一下我觉得还不错的题解,%%%
@[Bug_FreeOωO] AcWing 789. 数的范围
@[醉生梦死] AcWing 789. 二分算法的证明和边界分析
题目描述
给定一个按照升序排列的长度为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$
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1
算法1
(二分) $O(n)$
相信大家在没有学习算法的时候多多少少都对二分法有一定的了解,二分法在查找的过程通过和区间中间值进行比较从而确定所找的值是中间值还是在左区间或是右区间。
一个题目,如果一个区间具有单调性质,那么一定可以二分,但是如果说这道题目没有单调性质,而是具有某种区间性质的话,我们同样可以使用二分。
——yxc总
常见的可以二分的题
1. 在区间中找一个数
2. 本题中位置的最值即大于等于或小于等于某个值
3. 某个东西是否存在
4. 仔细分析题目意思2333
二分算法在选择区间时,选择的区间一定是存在答案的
做这道题可以直接上模板,但是模板有一些需要注意的地方
比如我们在 $1 2 2 3 4 5$ 中找到2在序列中第一次出现的位置和最后一次出现的位置,首先我们先确定中间下标,将区间划分为左右两个区间,然后根据check函数判断来更新区间,因为我们需要寻找第一次出现的位置,所以check我们可以这么写 if(a[mid] >= target) r = mid;
,那么最后一次出现就应该是 if(a[mid] <= target) l = mid;
。这里要注意一点,为什么会有两个模板呢,因为区间的更新有两种情况,第一种就是左区间包含mid,第二种就是左区间不包含mid,而我们计算mid的时候是 l + r >> 1
,当 $l + 1 = r$ 并且是第二种划分情况时,如果依然是 l + r >> 1
的话,会造成死循环,也就是TLE,区间会一直停留在原地,所以左区间不包含mid时应该向上取整。
当然这两个模板无论哪一个都可以求你要找的那个数,所以如果只出现一次的话哪个都行,也就不存在无限循环的情况!!!
贴上模板
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
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;
}
作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这里再说一下满足某种性质的区间,比如上面找2出现的位置,那么划分区间时,我们根据选的区间是否满足 $>= target$ 或者 $<= target$ 这一性质来判断我们找的数是不是在这个区间
时间复杂度 $O(n)$
C++ 代码
#include <cstdio>
using namespace std;
const int N = 1e5 + 10;
int n , q;
int a[N];
int main()
{
scanf("%d%d" , &n , &q);
for(int i = 0; i < n; ++ i) scanf("%d" , &a[i]);
while(q --)
{
int k;
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\n");
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\n" , l);
}
}
return 0;
}