文章目录
二分查找
在一个有序数组中用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;
}