第二周 二分法的运用

第二周学习了二分法,二分查找,二分答案

二分法的思路并不难,

比如说找一个1到100内的随机数a=38,

先用一次二分,分为1到50和51到100

让38与50相比,小于50,我们取前面一段,

再把1到50分为1到25,26到50

让38与25相比,大于25,取后面一段

25到50分为两段以此类推,就能找到a这个数

也可以无脑用循环查找,但如果数据再大一点,无脑循环花费的时间太多,内存占用也

二分法效率高好处就体现出来了

题目描述

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

输入格式

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

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

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

输出格式

输出一行,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

本题输入输出量较大,请使用较快的 IO 方式

二分查找,并输出所查找的数的编号

先写一个二分法

int left=1,right=10e9,mid;

while(left<=right){

  mid=(left+right)/2;

  if(mid>n) right=mid-1;

  else left=mid+1;

}

 while无限循环直到条件不符,

如果mid大于n  left------n-----mid--------right

那么我们就把right赋值为mid-1

如果mid小于n left------mid---n--------right

那么我们就把left赋值为mid+1

不断取中,从而得到最终答案

但是观察一下题目

如果我查询的是3这个数,根据题意我应该是要返回第一次出现3这个数的地方

那么我们又得去查找第一次出现3这个数的地方,这边可以直接在二分查找的过程中实现,或者用for循环从left循环到mid(我们在mid这个地方找到的3,那么就得从前面循环到mid找出第一次出现3的地方)也可以while循环查找,考虑到时间问题

二分查找>for循环查找>while循环查找

二分查找代码 

#include <bits/stdc++.h>
using namespace std;
int a[1000005];
int n,m;
int check(int f){
	int left=1,right=a[n],result=-1,mid;
	while(left<=right){
		mid=(left+right)/2;
		if(f==a[mid]){
			result=mid;
			right=mid-1;//把rigth赋值mid-1再一次缩小范围直到left大于right 
	    }//如果在里面还能找到位置更靠前的result就更新result 
	    else if(f<a[mid]) right=mid-1;
	    else left=mid+1;
	}
	return result;
}
int main(){
	cin>>n>>m;
	int g;
	a[0]=0;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=0;i<m;i++){
		cin>>g;
		cout<<check(g)<<" ";
	}
	return 0;
}

 OK我们看向下一题

Farmer John 建造了一个有 N(2 ≤ N ≤ 100000) 个隔间的牛棚,这些隔间分布在一条直线上,坐标是x1​ ,...,xN​ (0 ≤ xi≤ 1000000000)。

他的 C(2 ≤ C ≤ N) 头牛不满于隔间的位置分布,它们为牛棚里其他的牛的存在而愤怒。为了防止牛之间的互相打斗,Farmer John 想把这些牛安置在指定的隔间,所有牛中相邻两头的最近距离越大越好。那么,这个最大的最近距离是多少呢?

输入格式

第 1 行:两个用空格隔开的数字 N 和 C。

第 2 ~ N+1 行:每行一个整数,表示每个隔间的坐标。

输出格式

输出只有一行,即相邻两头牛最大的最近距离。

输入输出样例

5 3
1 
2 
8 
4 
9 

 输出样例

依旧题目要求,我们需要在n个牛棚里面给c头牛分配房间并且让各头牛之间的

相距最小的距离最大 

那么有的牛棚就得忽略掉了

如样例

1--2--3--4--5--6--7--8--9

1--1--0--1--0--0--0--1--1

可以放置的牛棚我们标志为1

放置三头牛,我们很容易就想到三头牛分别放在1,4,9是最合适的

其中最大的最小距离就是1到4,3那我们就输出3

数据一大起来我们就很难想到最佳的方案,我们可以把答案一个个带入用循环硬做出来

但是相比于二分法浪费的时间和空间太多

那我们下面就来讲解一下二分答案的思路

既然是把答案一个个带入进去,那我们就等于在一个范围里面找出一个最大的适合的值

那我们就可以用二分法来找出这个最大的适合的答案

怎么找

首先看例题,我们有五个牛棚,3头牛,所以我们只需要3个牛棚来放牛

建立一个数组a[5];

从left=1,right=a[5];开始二分

在这个区间里面找到最适合的答案

#include <bits/stdc++.h>
using namespace std;
int b[100005];
int m,n;
int check(int d){
	int e=m; 
	e--;//e--的原因就是第一头牛肯定是放在第一位 
	int jl=b[1];//定义一个jl变量来记录位置 
	for(int i=2;i<=n;i++){
		if((b[i]-jl)>=d) e--,jl=b[i];//当两个牛棚之间的距离能满足条件,放置一头牛再记录这个位置,进行下一次判断
		if(e==0) return 1;//当牛放完后,说明check函数能正常运行,这时候的mid可以实现 
	}
	return 0;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>b[i];		
	}
	sort(b+1,b+n+1);//输入后排序 
	int l=1,r=b[n],ans=0,mid;
	while(l<=r){
		mid=(l+r)/2;
		if(check(mid)) ans=mid,l=mid+1;//ans记录最佳答案 
		else r=mid-1;
	}
	cout<<ans;
	return 0;
}

第三题 跳石头

题目背景

一年一度的“跳石头”比赛又要开始了!

题目描述

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 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

 跳石头,和给小牛找牛棚的题目很相似,也是二分答案的一种类型

我们就可以把题目这么理解

0--2--11--14--17--21--25

0是起点,25是终点

中间有5个石头距离0这个位置的距离分别为2,11,14,17,21

我们选择挪掉两个石头仔细看,思考一下就可以得出把距离为2的这块和距离为14的这块挪掉就能得到最佳答案

依旧是二分答案,上面那题是选择牛棚,这边是选择挪掉石头,那我们也可以逆过来思考选择石头

所以与上面那题写法也就差不多了

#include <bits/stdc++.h>
using namespace std;
int a[50001];
int n,m,b;
int check(int d){
	int temp=0;
	int jl=a[0];
	for(int i=1;i<=m+1;i++){
		if((a[i]-jl)<d) temp++;//记录挪掉的石头的个数
		else jl=a[i]; //石头没有挪掉的话用jl来记录这个石头的位置
	}
	if(temp>b) return 0;//挪掉的石头多于规定的数,这个答案就是不可行的
	return 1;//答案可行,再还得继续二分找出最佳答案
}
int main(){
    cin>>n>>m>>b;//n是距离,m是石头数量,b是去掉的石头数 
    for(int i=1;i<=m;i++) cin>>a[i];
    a[0]=0;
    a[m+1]=n;
    sort(a+1,a+2+m);
    int start=0;
    int end=n;
    int l=start,r=end;
    int mid,ans;
    while(l<=r){
		mid=(l+r)/2;
    	if(check(mid)) ans=mid,l=mid+1;
   		else r=mid-1;
	}
	cout<<ans;
}

二分法的思路简单,但用途却很广泛

第二周 over

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值