在我们做编程题的过程中,常常会遇到一些题目数据范围过大,需要采用高效率的算法来解决。
二分算法就是一个O(log2 n)级别的算法
今天学到的模板我更愿意称之为备胎算法,因为这类题目都有一个共同点
1、答案应在一个区间、一个范围内,并且这个区间具有单调性
2、答案是满足条件的最小值或者最大值
3、有一个“备胎”用来存储满足条件的最佳值
只要满足这两个条件,我们就可以所以二分算法
我们先来看一下二分算法的基本应用,二分查找
这是最基础的利用二分算法进行查找的代码
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
int main()
{
int n,i;
scanf("%d",&n);
int a[n+1],l=1,r=n,mid,key,best=0;
for(i=1;i<=n;i++){
scanf("%d",&a[i]);
}
scanf("%d",&key);
while(l<=r){ //l==r 时也要进行比较
mid=(l+r)/2;
if(a[mid]==key){
best=mid;
break;
}
if(a[i]>key){ //答案应该在mid的左边
r=mid-1;
}else{ //答案应该在mid的右边
l=mid+1;
}
}
printf("%d\n",best);
return 0;
}
接下来我们来看几道二分算法的题目熟悉一下
18935 贪吃的小Q
时间限制:1000MS 代码长度限制:10KB
提交次数:0 通过次数:0
题型: 编程题 语言: G++;GCC
Description
腾讯2018春招技术类编程题 小Q的父母要出差N天,走之前给小Q留下了M块巧克力。 小Q决定每天吃的巧克力数量不少于前一天吃的一半,但是他又不想在父母回来之前的某一天没有巧克力吃, 请问他第一天最多能吃多少块巧克力?
输入格式
一行包含两个正整数,表示父母出差的天数N(N<=50000)和巧克力的数量M(N<=M<=100000)
输出格式
输出一个数表示小Q第一天最多能吃多少块巧克力。
输入样例
3 7
输出样例
4
#include <stdio.h>
#include <stdlib.h>
int main()
{
int N,M;
scanf("%d%d",&N,&M);
int l=1,r=M,mid,best=1,i,sum,day;
while(l<=r){
mid=(l+r)/2; //现在查找到mid值了
sum=M; //判断mid值是否满足条件
day=mid;
for(i=1;i<=N;i++){
sum=sum-day;
day=(day+1)/2;
}
if(sum>=0){ //如果mid值满足条件,那就是目前的最优值,用best保存这个最优值
best=mid,l=mid+1; //更改查找区间,看是否有更优值
}else{
r=mid-1;
}
}
printf("%d\n",best);
return 0;
}
分析:题目的答案是:第一天最多可以吃多少块巧克力,这意味着我们需要在[1,2,3,4,.........M-2,M-1,M]这个区间里面去找答案,我们第一天可以吃一块或者两块或者.... 最多M块全吃了;这个时候备胎best先初始化为1,因为第一天吃1块是最少的了,如果我们在后面的查找中找到满足条件的mid,那么mid肯定比best大,再用best保存目前的最优值就行了。
best保存最优值,这也是备胎名字的由来! 如果在接下来的查找中,有mid是满足条件的,那best就会被这个更优值给替代掉
mid查找完之后,更改查找区间,寻找是否有满足条件的更优值
9023 砍树
时间限制:1000MS 代码长度限制:10KB
提交次数:0 通过次数:0
题型: 编程题 语言: G++;GCC
Description
洛谷1873 伐木工人米尔科需要砍倒M米长的木材。这是一个对米尔科来说很容易的工作,因为他有一个漂亮的新伐木机,可以像野火一样砍倒森林。 不过,米尔科只被允许砍倒单行树木。 米尔科的伐木机工作过程如下:米尔科设置一个高度参数H(米),伐木机升起一个巨大的锯片到高度H, 并锯掉所有的树比H高的部分(当然,树木不高于H米的部分保持不变)。米尔科就行到树木被锯下的部分。 例如,如果一行树的高度分别为20,15,10和17,米尔科把锯片升到15米的高度, 切割后树木剩下的高度将是15,15,10和15,而米尔科将从第1棵树得到5米,从第4棵树得到2米,共得到7米木材。 米尔科非常关注生态保护,所以他不会砍掉过多的木材。这正是他为什么尽可能高地设定伐木机锯片的原因。 帮助米尔科找到伐木机锯片的最大的整数高度H,使得他能得到木材至少为M米。换句话说,如果再升高1米,则他将得不到M米木材。
输入格式
第1行:2个整数N和M,N表示树木的数量(1<=N<=1000000),M表示需要的木材总长度(1<=M<=2000000000) 第2行:N个整数表示每棵树的高度,值均不超过1000000000。所有木材长度之和大于M,因此必有解。
输出格式
第1行:1个整数,表示砍树的最高高度。
输入样例
5 20 4 42 40 26 46
输出样例
36
#include <stdio.h>
#include <stdlib.h>
int main()
{
int N,M;
scanf("%d%d",&N,&M);
int a[N+1],i;
for(i=1;i<=N;i++){
scanf("%d",&a[i]);
}
int l=1,r=1000000000,mid,best=0,sum;//初始化查找区间
while(l<=r){
mid=(l+r)/2; //查找到mid值
sum=0; //判断mid值是否满足条件
for(i=1;i<=N;i++){
if(a[i]>mid){
sum=sum+a[i]-mid;
}
}
if(sum>=M){ //mid值满足条件的话,用best保存最有优值
best=mid,l=mid+1; //更改查找区间,寻找是否有更优值
}else{
r=mid-1;
}
}
printf("%d\n",best);
return 0;
}
19050 牛牛打气球
时间限制:1000MS 代码长度限制:10KB
提交次数:0 通过次数:0
题型: 编程题 语言: 不限定
Description
贝壳找房2021届校招算法卷3 有n个气球,每个气球都有一个坚韧度,牛牛有一把全屏武器,可以使每一个气球的坚韧度都下降b(坚韧度不会为负数),特别的:每次释放武器的时候,牛牛可以选择一个气球,使得这个气球多承受a点伤害。 牛牛想知道,最少释放几次武器,可以使得所有气球的坚韧度都变成0呢?
输入格式
第一行三个整数n,a,b。 第二行n个空格隔开的整数,第个数表示第i个气球的坚韧度。 n<=500000,其余所有整数都在[1,10^9]范围内。
输出格式
一个整数表示答案。
输入样例
3 1 2 1 4 5
输出样例
2
提示
第一次释放选择对第三个气球多承受1点伤害,三个气球的坚韧度变成:0 2 2 。第二次释放后所有气球的坚韧度都为0。 注意观察数据范围,这类问题在进行计算时,很容易会超过int范围,所以尽量采用long long类型来存储和计算。
#include<stdio.h>
#include<stdlib.h>
int main()
{
long int n,a,b;
scanf("%ld%ld%ld",&n,&a,&b);
long int c[n+1],d[n+1],i;
for(i=1;i<=n;i++){
scanf("%ld",&c[i]);
}
long int l=1,r=1e9,mid,best=1e9,sum,cnt; //初始化查找区间
while(l<=r){
mid=(l+r)/2; //查找到mid值
sum=mid; //判断mid是否满足条件
cnt=0;
for(i=1;i<=n;i++){
d[i]=c[i]-b*mid;
while(d[i]>0&&sum>0){
d[i]=d[i]-a;
sum--;
}
if(d[i]>0){
cnt=cnt+d[i];
}
}
if(cnt==0){ //mid满足条件,用best保存最优值
best=mid,r=mid-1; //更改查找区间,寻找是否有更优值
}else{
l=mid+1;
}
}
printf("%ld\n",best);
return 0;
}
18725 宇宙迁跃
时间限制:1000MS 代码长度限制:10KB
提交次数:0 通过次数:0
题型: 编程题 语言: G++;GCC
Description
在基地的科学家发明“透镜”之后,宇宙航行变得更加效率。 作为基地元首的的代理人,你需要在K天内乘坐飞船到达首都川陀。 飞船可以花费一天时间,通过迁跃从一个星系到达另一个星系,但绝不能迁跃到星系之间,那样不但会遇到一些自然危险,也可能永远迷失。 我们把基地至川陀间星系的坐标看成是一个线性序列,例如a星系坐标是10,b星系坐标是15,那么飞船必须具备不小于5的迁跃能力才能从a航行至b。 基地坐标为0,请你根据基地至川陀间的N个星系坐标,计算飞船的迁跃能力至少为多大,才能在K天内(包含K天)到达川陀。
输入格式
第一行两个整数N和K。(1=<N<=10000,1=<K<=10000) 第二行N个整数,表示N个星系的坐标ai,题目确保坐标由小到大排列。(0=<ai<=100000)
输出格式
仅一行,飞船的最小迁跃能力。
输入样例
5 2 1 4 6 10 19
输出样例
10
提示
样例说明:川陀的坐标为最后一个值19。 飞船的迁跃能力至少为10,才能在2天内到达川陀。
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
int main()
{
int N,K;
scanf("%d%d",&N,&K);
int a[N+2],i;
for(i=1;i<=N;i++){
scanf("%d",&a[i]);
}
a[0]=0,a[N+1]=1000000;
int l=1,r=100000,mid,best=a[N],sum,day;
while(l<=r){
mid=(l+r)/2; //查找到mid值
sum=0; //判断mid值是否满足条件
day=0;
for(i=1;i<=N;i++){
if(mid>=(a[i]-sum)&&mid<(a[i+1]-sum)){
sum=a[i];
day++;
}
}
if(sum==a[N]&&day<=K){ //mid满足条件,best保存目前最优值
best=mid,r=mid-1; //更改查找区间,寻找是否有更优值
}else{
l=mid+1;
}
}
printf("%d\n",best);
return 0;
}
可以看到上面这几道题,都用到了一个模板
初始化查找区间
查找到mid值
判断mid值是否符合条件
best保存当前最优值
更改查找区间,寻找是否有更优值