二分查找

二分查找

在一个有序数组中用log(n)的时间复杂度发现目标值的算法叫作二分查找算法。
举个例子:
我们要查找32。
L是查找范围的下限,R是上限,mid是中间数位置
L=1,R=9,mid=(1+9)/2=5;
在这里插入图片描述
30<32,小了
在这里插入图片描述
L=6,R=9,mid=(6+9)/2=7余1=7;36>32,大了
在这里插入图片描述
L和R重合了,32找到了。
在这里插入图片描述
用代码实现:

int find(int a[],int k){//二分查找函数
	int L,R,mid;//定义
	L=1,R=n;//目前可查找的范围
	while(L<=R){
		mid=(L+R)/2;
		if(a[mid]==k){//找到了
			return mid;//返回k的位置
		}
		else if(a[mid]>k){//太大了
			R=mid-1;//把上限变小
		}
		else{//太小了
			L=mid+1;//把下限变小
		}
	}
	return -1;//没找到
}

整数二分

//在递增序列中查找<=x中最大的一个 最大化答案
while(L<R){
	int mid=(L+R+1)>>1;//求出中间位置
	if(a[mid]<=x){//小了或一样
		L=mid;
	}
	else{//大了
		R=mid-1;
	}
}
return a[L];
//在递增序列中查找>=x中最小的一个 最小化答案
while(L<R){
	int mid=(L+R)>>1;//求出中间位置
	if(a[mid]>=x){//大了或一样
		R=mid;
	}
	else{//小了
		L=mid+1;
	}
}
return a[L];

二分答案

假设最优解的评分为X,显然对于任意n>X,都不存在一个合法的方案达到n分,否则就与n的最优性矛盾;而对于任意的n<=X,一定存在一个合法的条件达到或超过n分。
这种问题有一种特殊的单调性——在X的一侧合法,在另一侧不合法,可以用二分找到这个分界点X。
下面来看几道题:

砍伐树木

题目描述

小华被小林叫去砍树,他需要砍倒m米长的木材。现在,小华弄到了一个奇怪的伐木机。伐木机工作过程如下:小华设置一个高度参数h(米),伐木机升起一个巨大的锯片到高度h,并锯掉所有的树比h高的部分(当然,树木不高于h米的部分保持不变)。小华就得到树木被锯下的部分。

例如,如果一行树的高度分别为20、15、10、17米,小华把锯片升到15米的高度,切割后树木剩下的高度将是15、15、10、15米,而小华将从第一棵树得到5米,从第4棵树得到2米,共得到7米木材

小华非常关注生态保护,所以他不会砍掉过多的木材。这正是他为什么要尽可能高的设定伐木机锯片的原因。帮助小华找到伐木机锯片的最大的整数高度h,使得他能得到的木材至少为m米。换句话说,如果再升高1米,则他将得不到m米木材

输入描述

第一行两个整数n和m,n表示树木的数量,m表示需要的木材总长度

第二行n个整数,表示每棵树的高度,值均不超过10^9.保证所有木材长度之和大于m,因此必然有解

输出描述

一行一个整数,表示砍树的最高高度

样例

输入
5 20
4 42 40 26 46
输出
36

解题思路

首先可以看出砍树的高度越高,被锯下的部分越少,用数组表示每个数的原始高度。
砍树高度的范围是多少?
最少是0
最大是树原始高度的最大值max
从小到大枚举h的值,并计算出对应的木材长度,枚举的过程可以用“二分答案”的方法快速得到结果。

代码

#include<iostream>
#include<algorithm>
using namespace std;
int main(){
    int n,a[1000005],x;//定义
    cin>>n>>x;//输入
    int l=0,r=0;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        r=max(r,a[i]);
    }
    while(l<r){//最大化答案
        long long mid=(l+r+1)/2;//求中间位置
        long long ans=0;
        for(int i=1;i<=n;i++){//求砍木材长度
            if(a[i]>mid){
                ans+=a[i]-mid;
            }
        }
        if(ans>=x){//满足条件
            l=mid;//提高伐木机高度
        }
        else{//木材不够
            r=mid-1;//减低伐木机高度
        }
    }
    cout<<l;//输出
    return 0;
}

月度开销

题目描述

农夫约翰是一个精明的会计师。他意识到自己可能没有足够的钱来维持农场的运转了。

他计算出并记录下了接下来 N (1 ≤ N ≤ 100,000) 天里每天需要的开销。

约翰打算为连续的M (1 ≤ M ≤ N) 个财政周期创建预算案,他把一个财政周期命名为fajo月。

每个fajo月包含一天或连续的多天,每天被恰好包含在一个fajo月里。

约翰的目标是合理安排每个fajo月包含的天数,使得开销最多的fajo月的开销尽可能少。

输入描述

第一行包含两个整数N,M,用单个空格隔开。

接下来N行,每行包含一个1到10000之间的整数,按顺序给出接下来N天里每天的开销。

输出描述

一个整数,即最大月度开销的最小值。

样例

输入
7 5
100
400
300
100
500
101
400
输出
500

解题思路

用二分法找最大月度开销的最小值,每找到一个中间值,判断能否生成M个fajo月。
按照贪心的思想,遍历每一天,当每月超出mid值的时候,才把当天放到下一个fajo月。

代码

#include<iostream>
#include<algorithm>
using namespace std;
int n,a[100005],x;//定义
int main(){
    cin>>n>>x;//输入
    int l=0,r=0;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        l=max(l,a[i]);
        r+=a[i];
    }
    while(l<r){//最小化答案
        long long mid=(l+r)/2;//求中间值
        long long num=0,cnt=1;
        for(int i=1;i<=n;i++){//求最少需要的fajo月数
            if(num+a[i]>mid){//判断是否超过mid值
                cnt++;//把当天放到下一个fajo月中
                num=a[i];
            }
            else{
                num+=a[i];
            }
        }
        if(cnt<=x){//fajo月数量不够
            r=mid;
        }
        else{//fajo月数量比预期的多
            l=mid+1;
        }
    }
    cout<<l;//输出
    return 0;
}

河中跳房子

题目描述

每年奶牛们都要举办各种特殊版本的跳房子比赛,包括在河里从一个岩石跳到另一个岩石。这项激动人心的活动在一条长长的笔直河道中进行,在起点和离起点L远 (1 ≤ L≤ 1,000,000,000) 的终点处均有一个岩石。在起点和终点之间,有N (0 ≤ N ≤ 50,000) 个岩石,每个岩石与起点的距离分别为Di (0 < Di < L)。
在比赛过程中,奶牛轮流从起点出发,尝试到达终点,每一步只能从一个岩石跳到另一个岩石。当然,实力不济的奶牛是没有办法完成目标的。

农夫约翰为他的奶牛们感到自豪并且年年都观看了这项比赛。但随着时间的推移,看着其他农夫的胆小奶牛们在相距很近的岩石之间缓慢前行,他感到非常厌烦。他计划移走一些岩石,使得从起点到终点的过程中,最短的跳跃距离最长。他可以移走除起点和终点外的至多M (0 ≤ M ≤ N) 个岩石。
请帮助约翰确定移走这些岩石后,最长可能的最短跳跃距离是多少?

输入描述

第一行包含三个整数L, N, M,相邻两个整数之间用单个空格隔开。
接下来N行,每行一个整数,表示每个岩石与起点的距离。岩石按与起点距离从近到远给出,且不会有两个岩石出现在同一个位置。

输出描述

一个整数,最长可能的最短跳跃距离

样例

输入
25 5 2
2
11
14
17
21
输出
4

解题思路

用二分查找的方法。如果mid值是最长可能的最短跳跃距离,那么两个岩石之间的距离小于mid的必须移走,进而求出需要多少块岩石。
以起点为left,终点为right,取中间值为mid,计算出需要移走岩石的个数,最终找到移走多少块岩石。

代码

#include<iostream>
using namespace std;
int a[50006],n;//定义
int fun(int mid){//计算需要移走岩石的个数
    int cou=0,pre=0;
    for(int i=1;i<=n;i++){
        if(a[i]-a[pre]<mid){
            cou++;
        }
        else{
            pre=i;
        }
    }
    return cou;
}
int main(){
    int m,l,le,ri,mid,cou;//定义
    cin>>l>>n>>m;//输入
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    n++;
    a[n]=l;
    le=0;
    ri=l;
    while(le<ri){
        mid=(le+ri+1)/2;
        cou=fun(mid);
        if(cou>m){
            ri=mid-1;
        }
        else{
            le=mid;
        }
    }
    cout<<le;//输出
    return 0;
}

好斗的奶牛

题目描述

农夫约翰搭了一间有N个牛舍的小屋,牛舍排在一条线上,第i号牛舍在xi的位置。但是有M头牛对小屋很不满意。因此经常互相攻击。约翰为了防止牛之间相互伤害,因此决定把每头牛都放在离其他牛舍尽可能远的牛舍。

输入描述

第一行输入n(n<=1000)和m(m<=n)

接下来的n行有N个数,第i行为对应的xi

输出描述

输出距离最近的两头奶牛间的距离的最大值。

样例

输入
5 3
1
2
8
4
9
输出
3

做题思路

我们先简化一下题目:有n个牛栏,选m个里面放进牛,相当于一条线段上有n个点,选取m个点,使得m个点之间的最小距离最大。

代码

#include<iostream>
#include<algorithm>
using namespace std;
int a[1005],n;//定义
int fun(int mid){
    int cnt=1,pre=1;
    for(int i=2;i<=n;i++){
        if(a[i]-a[pre]>=mid){
            cnt++;
            pre=i;
        }
    }
    return cnt;
}
int main(){
    int m,l,r,mid,cnt;//定义
    cin>>n>>m;//输入
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    sort(a+1,a+n+1);//排序
    l=0;
    r=a[n];
    while(l<r){//二分答案
        mid=(l+r+1)/2;
        cnt=fun(mid);
        if(cnt>=m){
            l=mid;
        }
        else{
            r=mid-1;
        }
    }
    cout<<l;//输出
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值