二分专题学习笔记~~~

1.简单二分入门模板

情景导入:

相对于传统的遍历搜索,二分的查找效率可谓是呈指数型的碾压,一个数据让你感受到二分的魅力,2^32=4294967296,但二分查找要求被查找的对象有一定的规律,接下来看一个二分入门级的例题。

例题:

题目:

二分查找

时间限制:C/C++ 1000MS,其他语言 2000MS
内存限制:C/C++ 256MB,其他语言 512MB
难度: 简单

描述

输入 n 个不超过 10^9 的非负整数(不一定是排好序的) a1,a2,…,an然后进行 m 次询问。对于每次询问,给出一个整数 q,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 -1。

输入描述

第一行 2 个整数 n 和 m,表示数字个数和询问次数。

第二行 n 个整数,表示这些待查询的数字。

第三行 m 个整数,表示询问这些数字的编号,从 1 开始编号。

输出描述

输出m行,m 个整数,表示答案。

用例输入 1 

11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6

用例输出 1 

1
2 
-1 

提示

1 <= n <= 10^6, 0 <= ai , q <= 10^9, 1 <= m <= 10^5

分析:

如果之前没接触过二分,可能就直接暴力遍历来求解,但这样效率非常慢,一定会超时的,因为这个数组是从小到大排序的,所以正好可以用二分来解决。就是通过区间中间位置的值来和目标值进行比较来选取一半区间,舍弃一半区间。这个题的代码就不发了,后面发模板。

二分模板:

代码:

	int l=0,r=100;
	while(l+1<r)
	{
		int mid=(l+r)/2;
		if(mid>x)r=mid;//if里面加不加 = 决定着最后找的结果
		else l=mid;
	}

二分的模板网上其实有很多,这个是我一直用的模板,感觉用起来比较方便,改上下界也比较灵活,就是改一下if里面的判断条件。

解析:

我用的这个代码最后l和r的关系为l+1=r,而最后答案是l还是r取决于if里面的条件,简单的说就是等号取到哪边答案就在哪边,就拿上面的模板写的举例,因为r对应的if里面没有等号,那l对应的条件有=,所以答案应该是l,本人语文功底有限,看不懂也没关系,自己模拟一下就明白了,后面要能灵活运用,这个是基本功,如果基本功不牢就会老是找错界。

几种基本应用场景:

(有二分基础的可以跳过)

1.非递减数列中查找某个值的位置(非递增数列也是一个原理)

代码1:找目标值最后出现的位置(左边界往右收缩)

	int l=0,r=100;
	while(l+1<r)
	{
		int mid=(l+r)/2;
		if(a[mid]>x)r=mid;
		else l=mid;
	}
	printf("%d\n",l);

代码2: 找目标值第一次出现的位置(右边界往左收缩)

	int l=0,r=100;
	while(l+1<r)
	{
		int mid=(l+r)/2;
		if(a[mid]>=x)r=mid;
		else l=mid;
	}
	printf("%d\n",r);

2.非递减数列中找小于等于目标值的最大值的位置(找大于等于目标值的最小值的位置原理也一样)

	int l=0,r=100;
	while(l+1<r)
	{
		int mid=(l+r)/2;
		if(a[mid]>x)r=mid;
		else l=mid;
	}
	printf("%d\n",l);

 解析:

如果数组中存在目标值输出的是目标值最后出现的位置,如果数组中无目标值输出的是小于目标值的最大值的位置。

小结:这些看看了解就行,只有实践才能快速提升。

进阶之二分答案:

介绍:

所谓二分答案不过是以二分算法为基础的一个进阶变形应用,在我看来就是把原来 if 里面的判断条件灵活化了,来解决更多的问题,习惯把判断条件写成一个check函数,接下来看一个例题来感受一下二分答案。

例题1:

洛谷P2440 木材加工

题目描述

木材厂有 n 根原木,现在想把这些木头切割成 k 段长度为 l 的小段木头(木头有可能有剩余)。

当然,我们希望得到的小段木头越长越好,请求出 l 的最大值。

木头长度的单位是 cm,原木的长度都是正整数,我们要求切割得到的小段木头的长度也是正整数。

例如有两根原木长度分别为 11 和 21,要求切割成等长的 6 段,很明显能切割出来的小段木头长度最长为 5。

输入格式

第一行是两个正整数 n,k,分别表示原木的数量,需要得到的小段的数量。

接下来 n 行,每行一个正整数 Li​,表示一根原木的长度。

输出格式

仅一行,即 l 的最大值。

如果连 1cm1cm 长的小段都切不出来,输出 0

输入输出样例
输入 
3 7
232
124
456
输出 
114
数据规模与约定

对于 100%100% 的数据,有 1≤n≤1e5,1≤k≤1e8,1≤Li≤1e8。

分析:

这个题要求的是在保证数量的前提下能每一小段长度的最大值,这个也是一个典型的比较简单的二分答案题,而二分答案的关键就是check函数如何写。

check函数怎么写:

思考:先明确最后题目要的答案是什么,这个题中最后要的是小段木头的长度的最大值,我们可以先把最初的模板敲出来
	int l=0,r=1e8+1;
	while(l+1<r)
	{
		int mid=(l+r)/2;
		if(check(mid)) l=mid;
		else r=mid;
	}

目标明确了之后,思路就清晰起来了,就是写一个check来检查mid成不成立

bool check(int x)
{
	int sum=0;
	for(int i=1;i<=n;i++)
	{
		sum+=a[i]/x;
	}
	if(sum>=k)return 1;
	else return 0;
}

完整代码:

#include<iostream>
using namespace std;
const int N=2e6;
int a[N];
int n,k;
bool check(int x)
{
	int sum=0;
	for(int i=1;i<=n;i++)
	{
		sum+=a[i]/x;
	}
	if(sum>=k)return 1;
	else return 0;
}
int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	int l=0,r=1e8+1;
	while(l+1<r)
	{
		int mid=(l+r)/2;
		if(check(mid)) l=mid;
		else r=mid;
	}
	printf("%d\n",l);
}

小结:

这其中其实还有很多细节,就比如上下界的问题,还有某些地方要不要写等号,以及最后输出的是l还是r等等,这些细节就不细说了,得自己悟,如果有问题欢迎来交流。

总结:

这里我就不写那么多例题了,主要是讲一个思路,想要真正掌握还得在刷题时多多思考,总结规律,小趴菜努力学习中ing~~~

  • 27
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值