二分查找:
适用于有序数组(正序或者逆序)
对于无序数组可以调用sort函数。
其时间复杂度为O(log'n),
远远小于遍历查找的O(n).
关键:查找的区间是不断迭代的,所以确定查找的范围十分重要,主要就是左右区间的开和闭的问题,开闭不一样,对应的迭代方式也不一样。
我一般用(蓝红标记法)
流程一般如下
l(左)=-1;r(右)=N:保证最后一定可以迭代成功,防止特殊情况
(转载自b站up:五点七边)
这种方法的优点:易懂且不会出现死循环。
二分法最重要的两个点,就是循环条件和后续的区间赋值问题。
因为两者是相互联系,相互影响的,所以就需要两者统一,如果两者不统一,就会出现问题
例题1(洛谷P2249)
题目描述
输入 nn 个不超过 10^9109 的单调不减的(就是后面的数字不小于前面的数字)非负整数 a_1,a_2,\dots,a_{n}a1,a2,…,an,然后进行 mm 次询问。对于每次询问,给出一个整数 qq,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 -1−1 。
输入格式
第一行 22 个整数 nn 和 mm,表示数字个数和询问次数。
第二行 nn 个整数,表示这些待查询的数字。
第三行 mm 个整数,表示询问这些数字的编号,从 11 开始编号。
输出格式
输出一行,mm 个整数,以空格隔开,表示答案。
输入输出样例
输入 #1
11 3 1 3 3 3 5 7 9 11 13 15 15 1 3 6输出 #1
1 2 -1
初学二分法时
我先尝试了该写法
#include<bits/stdc++.h>
using namespace std;
int main()
{ int n1,n2;
cin>>n1>>n2;
int shu[n1+1];
shu[0]=-1;
for (int i=1;i<n1+1;i++)
cin>>shu[i];
int temp;
for(int i=0;i<n2;i++)
{
cin>>temp;
int l=1,r=n1,mid;
while(l<=r)
{
mid=(l+r)/2;
if(temp==shu[mid])
{
while(temp==shu[mid-1])
{mid--;
}
cout<<mid<<" ";break;
}
else if (temp<shu[mid])
{r=mid-1;
}
else if (temp>shu[mid])
{l=mid+1;
}
}
if(l>r) cout<<"-1 ";
}
}
在查找数的时候使用二分查找,在查找这个数字在序列中第一次出现的编号使用倒序扁遍历
但是最后一个测试点超时
因为若有特殊数列(1,1,1,1,.....,1)在查找第一次出现的编号时的复杂度也变为O(n)
因此要在查找第一次出现的编号时也要使用二分法
AC代码如下:
#include<bits/stdc++.h>
using namespace std;
int main()
{ int n1,n2;
cin>>n1>>n2;
int shu[n1+1];
shu[0]=-1;
for (int i=1;i<n1+1;i++)
cin>>shu[i];
int temp;
for(int i=0;i<n2;i++)
{
cin>>temp;
int l=1,r=n1,mid;
while(l<r)
{
mid=(l+r)/2;
if(shu[mid]>=temp)
r=mid;
if(shu[mid]<temp)
l=mid+1;
}
if(shu[l]==temp)
cout<<l<<" ";
else cout<<-1<<" ";
}}
二分答案:
与二分查找的思想相同:先确定答案的范围 在有二分法迭代缩小范围找到最终答案。
适用特征:1、答案存在单调性
2、假设可以通过自定义check函数进行验证
3、最大中的最小,或者最小中的最大。
例题:
1、(洛谷 P1824 进击的奶牛)
题目描述
Farmer John 建造了一个有 NN(22 \le≤ NN \le≤ 100000100000) 个隔间的牛棚,这些隔间分布在一条直线上,坐标是 x_1x1 ,...,x_NxN (0 \le≤ x_ixi \le≤ 10000000001000000000)。
他的 CC(22 \le≤ CC \le≤ NN) 头牛不满于隔间的位置分布,它们为牛棚里其他的牛的存在而愤怒。为了防止牛之间的互相打斗,Farmer John 想把这些牛安置在指定的隔间,所有牛中相邻两头的最近距离越大越好。那么,这个最大的最近距离是多少呢?
输入格式
第 11 行:两个用空格隔开的数字 NN 和 CC。
第 22 ~ N+1N+1 行:每行一个整数,表示每个隔间的坐标。
输出格式
输出只有一行,即相邻两头牛最大的最近距离。
输入输出样例
输入 #1
5 3 1 2 8 4 9输出 #1
3
本题第一次尝试时 博主感觉脑子有大病
将序号进行二分 试图求出最优解的第二个牛棚的序号。
错误代码如下:
#include<bits/stdc++.h>
using namespace std;
int mid;
long int shu[100005],n,c,l,r;
int check(int midd)
{ int cou=1,x=0;
int dis=shu[midd]-shu[0];
for(int i=0;i<n;i++)
{if(shu[i]-shu[x]>=dis)
{
cou++;x=i;
}
}
if(cou>=c) {
return 1;
}
else return 0;
}
int main()
{
cin>>n>>c;
for(int i=0;i<n;i++)
cin>>shu[i];
sort(shu,shu+n);
l=-1;r=n;
while(l+1!=r)
{
mid=(r+l)/2;
if(check(mid))
l=mid;
else r=mid;
}
cout<<shu[l]-shu[0];
}
试想(若牛棚位置是 (1 6 8 11)牛数为3,则最优放置是1,6,11
但是最小距离是11-8=3而不是第二个牛棚的坐标减去第一个牛棚
因此,该思路错误
正确思路是将最小距离的数值进行二分处理
AC代码如下
#include<bits/stdc++.h>
using namespace std;
int mid;
long int shu[100005],n,c,l,r;
int check(int mid2)
{ int cnt=1,x=1;
for(int i=1;i<=n;i++)
if(shu[i]-shu[x]>=mid2) {
cnt++;
x=i;
}
if(cnt<c) return 1;
else return 0;
}
int main()
{
cin>>n>>c;
for(int i=1;i<n+1;i++)
cin>>shu[i];
sort(shu+1,shu+n+1);
l=0;r=shu[n-1]-shu[0]+1;
while(l+1!=r)
{
mid=(r+l)/2;
if(check(mid))
r=mid;
else l=mid;
}
cout<<l;
}
2、洛谷 P2678 [NOIP2015 提高组] 跳石头
题目背景
一年一度的“跳石头”比赛又要开始了!
题目描述
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 NN 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 MM 块岩石(不能移走起点和终点的岩石)。
输入格式
第一行包含三个整数 L,N,ML,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 L \geq 1L≥1 且 N \geq M \geq 0N≥M≥0。
接下来 NN 行,每行一个整数,第 ii 行的整数 D_i( 0 < D_i < L)Di(0<Di<L), 表示第 ii 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。
输出格式
一个整数,即最短跳跃距离的最大值。
输入输出样例
输入 #1复制
25 5 2 2 11 14 17 21输出 #1复制
4说明/提示
输入输出样例 1 说明
将与起点距离为 22和 1414 的两个岩石移走后,最短的跳跃距离为 44(从与起点距离 1717 的岩石跳到距离 2121 的岩石,或者从距离 2121 的岩石跳到终点)。
数据规模与约定
对于 20\%20%的数据,0 \le M \le N \le 100≤M≤N≤10。
对于 50\%50% 的数据,0 \le M \le N \le 1000≤M≤N≤100。
对于 100\%100%的数据,0 \le M \le N \le 50000,1 \le L \le 10^90≤M≤N≤50000,1≤L≤109。
本题和例1较相似
也可以套用例1的模板
只是多了起点和终点
AC代码如下
#include<bits/stdc++.h>
using namespace std;
int mid;
long int shu[100005],n,c,l,r;
int check(int mid2)
{ int cnt=0,x=0;
for(int i=1;i<=n+1;i++)
if(shu[i]-shu[x]<mid2)
{
cnt++;
}
else x=i;
if(cnt<=c) return 1;
else return 0;
}
int main()
{ int temp;
shu[0]=0;
cin>>temp>>n>>c;
shu[n+1]=temp;
for(int i=1;i<n+1;i++)
cin>>shu[i];
l=0;r=shu[n+1]-shu[0]+1;
while(l+1!=r)
{
mid=(r+l)/2;
if(check(mid))
l=mid;
else r=mid;
}
cout<<l;
}