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~~~