数的范围(二分,不用考虑边界问题版,点击就会)

二分

就是找单调性

用二分查找元素要求数组的有序性或者拥有类似有序的性质。

只有所要寻找的数组能够满足某一条件而被分成两边,就可以进行二分。

模板:

bool check(int x) {/* ... */} //检查x是否满足某种性质

int L=-1,R=n;
while(L+1!=R)
{
	int mid=L+R>>1;
	if(check()) L=mid;
	else R=mid;
	//最后根据你所分左右两边区间的结果
	//选取L或者R作为结果
}

  • L的初始值为-1,R的初始值为N
       首先,如果二分本来就没有结果的话,程序是一定有结果的。
    比如1 2 2 3 3 4,,如果你要寻找第一个 >=5 的数,你会发现,整个过程都在执行L=mid,最后得到的结果中,R是等于下标6的,他明显这个时候是越界的,说明我们找不到要寻找的数字,而如果我们一开始将R赋值为n-1,也就是赋值为下标5的时候,他返回的R是5,是没有越界的,被我们当成了答案,但其实这时候我们的二分是没有答案的,就发生了错误;
       其次,L最小值为-1,R最小值只能取到1,因为L+1!=R为循环结束条件,R最大值为N,同理则L的最大值为N-2,则(L+R)/2的取值范围是 [0,N)
       mid的值始终位于0到N的左闭右开区间里面,不会发生越界的错误;

  • 循环结束的条件是while(L+1!=R)
       之前学的二分,他循环结束的条件是while(L<R)
       而这边给出的循环条件是while(L+1!=R) 其实,就是当L和R相邻的时候,循环就结束,而原本的while(L<R)
    是当两区间重合以后,循环才结束,所以之前我们需要判断对mid进行加一或者减一的操作,而且因为区间重合的问题,最后返回的L、R还要再进行判断,而这边的这个二分,因为区间反回的是不重合的两区间,只有L=mid和R=mid这两种情况,最后根据需要返回L或者R;

​ 不会陷入死循环
   对于比较奇葩的情况,比如数组大小为1或者2
   比如int a[1],b[2];
   由于我们是while(L+1!=R)结束循环,也就是当L和R相邻的时候结束条件
   对于a[1],他的下标为0 此时L=-1,R=n也就是1
   对于b[2],他的下标为0,1 此时L=-1,R=n也就是2
   所以无论何种情况,初始的L+1始终小于R,历经循环后最终L和R相邻,不会出现一开始L就和R重合等情况导致出现while(L+1!=R)循环不能结束的情况

最后
   我们就能够通过二分得到不重合的两区间,而且只需要L=mid和R=mid,不需要考虑L=mid+1,R=mid-1的情况
   且记得一开始对于一个下标为0,1,2…n-1的数组,指针L要赋值为-1,指针R要赋值为n

浮点数二分

bool check(double x) {/* ... */} //检查x是否满足某种性质

double bsearch_3(double l, double r)
{
    const double eps = 1e-6; //eps表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    retrun l;
}

题目

给定一个按照升序排列的长度为 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

AC代码(思路在注释)

/**
 * 二分经典题
 *  用二分查找元素要求数组的有序性或者拥有类似有序的性质。
 * 只有所要寻找的数组能够满足某一条件而被分成两边,就可以进行二分。 
 *  
 * 题目要求:
 *  在数组里面查找某元素,找不到输出-1,
 * 找到了输出不小于该元素的最小位置和不大于该元素的最大位置 
 * 
 * 也就是找两个二分:一个>=x的第一个数,一个<=x的最后一个数 
 **/
 

#include<iostream>

using namespace std;

const int N = 1e5 + 10;
int n, q;
int a[N];




int main(){
	cin >> n >> q;
	for (int i = 0; i < n; i++) cin >> a[i];
	while (q--)
	{
		int x;
		cin >> x;
		int l = -1, r = n;
		while (l + 1 != r){
			int mid = (l + r) / 2;
			if (a[mid] >= x) r = mid;
			else l = mid;
		}
		if (a[r] != x){
			cout << "-1 -1" << endl;
		}
		else{
			cout << r << ' ';
			int ll = -1, rr = n;
			while (ll + 1 != rr){
				int mid = (ll + rr) / 2;
				if (a[mid] <= x) ll = mid;
				else rr = mid;
			}
			if (a[ll] != x) cout << r << endl;
			else cout << ll << endl;
		}
	}
	return 0;
}
  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值