1前言
二分想必大家都会吧,请看如下代码**(求出单调序列中最先出现的m)**
int find(int x){
int l = 1,r = n;
while(l<r){
int mid = (l+r+1)>>1;
if(a[mid]>x){
r = mid-1;
}else{
l = mid;
}
}
if(a[r]==x){
return r;
}else{
return -1;
}
}
请指出错误并改正,是不是很难一眼看出来
二分是一个“玄学”算法
思路可以想对,但是一写代码就调不对,本文将治理你的各种疑难杂症
2典型的错误
作者在调二分题的时候,出现的问题可以概括为两种
1陷入死循环
2答案错误
我们将分别解决
3为什么错误-问题的分类
首先,二分分为整数二分和浮点数二分
整数二分也有两种,区间左端的值和区间右端的值
很多错误都是混淆了这些问题而导致的
4分类解决问题
(1)整数二分
我们运行前言中的代码,发现求出的是右端值,和求左端值哪里不同呢,我们逐条语句分析
首先,mid的取值,int型变量的除法是自动向下取整的,如果l+r是偶数,再加一不会有影响
关键在于l+r是奇数的情况,究竟该向下取整还是向上取整呢?
如果l,r相差得多,mid取什么没有影响,那么我们假设此时l+1 = r
如果向下取整,mid = l,如果mid正好符合要求,那么l一定要更新到mid+1,不然l,r,mid都不变,就导致了死循环
所以如果向下取mid,则l+1,如果向上取mid,则r-1
但是我们该如何选取这两种做法呢?
我们考虑mid恰好符合题意
如果要分出左端,就要更新r,否则更新l
但是对于上述问题,如果选取mid向上取整,r-1,就跳过了这个合法情况
如果r-1不合法呢,直接就输出无解了
所以,我们总结一下
遇到二分问题,先要看合法情况,分出左端r = mid,分出右端l = mid,r = mid,mid向下取整,l = mid,mid向上取整
分出右端的二分模板上文已经给出,这里附分出左端的代码(c++)
int find(int 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){
return l;
}else{
return -1;
}
}
(2)浮点数二分
我们依旧采用上面的思考方式,这里可以考虑精度,把l<r改成(r-l)<=1e-6
浮点数二分好考虑多了,mid设为(l+r)/2即可,因为浮点数不用考虑取整
因为不是整数,所以没有区间问题,分的是一个点,所以l,r均不用+1,最后,相等情况直接输出就行
附代码(c++)
while((r-l)>=eps){
double mid = (l+r)/2;
if(check(mid)){
l = mid;
}else{
r = mid;
}
}
5后记
二分成为玄学,其实是因为二分的细节太多,把问题类型搞明白,一定有一个完整的思考过程
不要直接背模板!不要直接背模板!不要直接背模板!没人能背下来!
最后,如果熟练了,模板自然形成,不用再调试“玄学”的二分了
本文作者是蒟蒻,如有错误请各位神犇指点!
森林古猿出品,必属精品,请认准CSDN森林古猿1!