ACM预备队week2二分查找与二分答案

二分查找

适用于有序数组(正序或者逆序)

对于无序数组可以调用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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值