二分查找与二分答案————细致解读,让你不在困惑

在我们的平常练习中,有时候被二分弄的找不到方向,甚至题解看起来不像二分,非常苦恼,看完这篇文章,将把二分学透;

二分 分为查找和答案,两个类型;

其实二分也是有模板的,下面来看一下二分的模板

模板一

    while (l < r){
        int mid = l + r >> 1; //这里相当于一个l+r/2,这样写是因为可以更好的节约空间;
        if (check(mid))  r = mid;  //这里的check是一个函数,用来判断;
        else l = mid + 1;
    }
 

模板二 

    while (l < r){
        int mid = l + r + 1 >> 1;  //这里的+1是为了防止死循环

        if (check(mid))  l = mid;
        else r = mid - 1;
    }
 

 模板一是尽量向左边去查找,模板二是尽量向右边去查找

大多数的二分形式都是  

int l = 1, r = n;
while (l <= r) {
	int mid = l + r >> 1;
	//m为要查找的数
	if (a[mid] == m){
		cout << mid;
		break;
	}
	if (a[mid] < m)
		r = mid - 1;
	if(a[mid]>m)
		l = mid + 1;
}

这种遇到复杂的问题,往往难以理解,所以建议用上面的模板解决,但是依然是需要去理解,不然只用模板,遇到问题也往往只有上文,没有下文 

下面我们做几个题目去理解这两个模板

 

例题一:数组的二分查找之一

这是二分入门的查找问题,首先我们看是输出最小的那一个,就明白,应该向左去寻找,因为二分是在有单调性的序列中才能使用 ,又看见题目里面要求 ai>=x ,所以我们可以下出下面代码

在看题解之前可以自己先用模板写一下,这样效果更好

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
int n, x, k;
int a[10000000];
int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	cin >> k;
	while (k--) {
		cin >> x;
		int l = 1, r = n;//左右边界
		while (l < r) {          //模板一,往左边找
			int mid = l + r >> 1;
			if (a[mid] >= x) r = mid;//如果当前值大于等于目标,符合但是不是最小,继续往左边走
			else l = mid+1;
		}
		if (a[l] != x&&a[l]<x) { //
			cout << "no" << endl;//如果最后找的结果不符合,输出“no"
			continue;
		}
		cout << l << endl;//上面条件不成立,到这里直接输出结果就行
	}
	return 0;
}

经过这个题目,你大概有些了解,相当于跨进了二分的大门了 

 下面进行一个变式,希望可以用上面的模板和思路解决

例题二 数组的二分查找2

 

还是先进行分析 ,这个题目唯一变的就是查找的条件变了,所以很简单,以下代码给出

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
int n, x;
int a[10000000];
int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	int k;
	cin >> k;
	while (k--) {
		cin >> x;
		int l = 1, r = n;
		while (l < r) {
			int mid = l + r >> 1;//和上一个题一样,往左边来,因为最小
			if (a[mid] > x) r = mid;//这里的等于号不能用了,因为要求变了
			else l = mid + 1;
		}
		if ( a[l] <= x) {
			cout << "no" << endl;
			continue;
		}
		cout << l << endl;
	}
	return 0;
}

相信这个题这是对上一个题的巩固和练习,不过相信你们还不不过瘾,所以。。。

例题三 数组的二分查找3

 

这里得看清楚题目,题目说的是最大的,所以应该往右边去查找

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
int n, x;
int a[10000000];
int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	int k;
	cin >> k;
	while (k--) {
		cin >> x;
		int l = 1, r = n;
		while (l < r) {
			int mid = l + r + 1 >> 1;//往右边查找
			if (a[mid] <= x) l = mid;//如果值小于等于目标值,说明在目标值左边,往右来,只有这样才能找到最大的那一个
			else r = mid - 1;//相反就往左
		}
		if (a[l] != x) {//如果找不到这个值
			cout << "no" << endl;
			continue;
		}
		cout << l << endl;
	}
	return 0;
}

 经过这几个题目的练习,相信大家对查找应该没什么大问题,剩下的就是巩固和练习

https://www.luogu.com.cn/problem/P1102

下面我们进入二分答案的世界,话不多说直接进入例题练习

例题一 砍树 

 

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
using namespace std;
int a[10000005], n, m, maxa, sum;
int check(int mid) {
	for (int i = 1; i <= n; i++) {
		if (a[i] > mid) sum += a[i] - mid;
	}
	if (sum >= m)return 1;//如果sum>=m,说明砍多了,也就是锯子的高度太低了,所以我们要放高一点
	else return 0;
}

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		if (a[i] > maxa) maxa = a[i];
	}
	int l = 0, r = maxa;
	while (l < r) {
		long long mid = l + r + 1 >> 1;//因为我们砍太多了不行,砍少了也不行,也就是最大值的意思
		sum = 0;//这里很容易忽略,每次都要清零
		if (check(mid)) l = mid;
		else r = mid - 1;
	}
	cout << l << endl;
	return 0;
}





经过这个题目就可以知道,其实二分答案与二分查找的区别就在于,二分答案在每次查找中都去比较运算一遍,而二分查找则是一直查找知道找出答案 

例题二 爱独居的野猫

 

首先看题,他说什么?最大的最近距离,最近?模板一,尽量往左边去找,判断条件就是区间距离>=mid,这样才能使mid最小,也就是最近。 

有了上一题的经验,我们可以马上敲出 

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
using namespace std;
int n, c, a[100005], maxa;
int check(int mid) {
	int cnt = 1;
	int i = 1, now = 1;
	while (i < n) {  //这里的处理比较巧妙,也就是第一个和第二个,第二个和第三个依次比较,大家也可以用其他方法
		i++;
		if (a[i] - a[now] >= mid)//如果大于mid,才能保证mid是最小值,cnt++;
			cnt++, now = i;
	}
	if (cnt < c)  return 1;//如果cnt<c说明mid取大了,导致放完第二个猫以后,已经到了最后位置,无法放第三个猫
	else return 0;
}
int main()
{
	cin >> n >> c;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	sort(a + 1, a + 1 + n);
	int l = 0, r = a[n];
	while (l < r) {
		int mid = l + r >> 1;
		if (check(mid)) r = mid;//因为mid取大了,所以我们得往左移动
		else  l = mid + 1;//取小了就往右边走
	}
	cout << l-1;//这里的-1是因为可能最后是l=mid+1,要-1才能得到正确答案(没有验证,仅个人理解)
	return 0;
}

例题三:数列分段2

 

一样还是最大的值最小,下面就不给出分析了,这个题跟上一个题目大致相同 

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
long long n, m, a[100005], maxa, mina = 1e9+1, sum;

int check(int mid) {
	long long cnt = 1, ans = 0;//cnt之所以初始化为1,是你第一个数必成一段
	for (int i = 1; i <= n-1; i++) {
		ans += a[i];
		if (ans + a[i + 1] >= mid) cnt++, ans = 0;//因为是最小值,所以要大于mid,
	}
	if (cnt <= m) return 1;//分段少了,也就是mid的值比较大,所以得小一点
	 return 0;
}


int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> a[i], sum += a[i];
		if (a[i] > maxa) maxa = a[i];

	}
	int l = maxa, r = sum;//注意这里的范围取值
	while (l < r) {
		int mid = l + r >> 1;
		if (check(mid))r = mid;
		else l = mid + 1;
	}
	cout << l-1;//上一个题目里面提到过
	return 0;
}

 有了上面的题目相信你对二分已经有了比较深的了解,其实二分答案都是大同小异,主要的就是check函数里面对题目要求的编写,这个地方要想清楚,下面给几个题目大家自己去完成,题目答案,大家在csdn也可以搜出

 1.进击的奶牛

2.最佳牛围栏

这两个模板,还需要大家去多练习,每道题目都有自己的处理方式,这只是为了更加去理解和解决,关键还是在自己的处理上面,希望大家可以有收获

如果有不懂的地方可以留言,一起交流进步,我也是刚刚接触 

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值