暑假算法刷题日记 Day 6

今天继续刷完二分查找,还有最后五个题二分查找就结束啦!

题目背景

小鸟有 n n n 个可同时使用的设备。

题目描述

i i i 个设备每秒消耗 a i a_i ai 个单位能量。能量的使用是连续的,也就是说能量不是某时刻突然消耗的,而是匀速消耗。也就是说,对于任意实数,在 k k k 秒内消耗的能量均为 k × a i k\times a_i k×ai 单位。在开始的时候第 i i i 个设备里存储着 b i b_i bi 个单位能量。

同时小鸟又有一个可以给任意一个设备充电的充电宝,每秒可以给接通的设备充能 p p p 个单位,充能也是连续的,不再赘述。你可以在任意时间给任意一个设备充能,从一个设备切换到另一个设备的时间忽略不计。

小鸟想把这些设备一起使用,直到其中有设备能量降为 0 0 0。所以小鸟想知道,在充电器的作用下,她最多能将这些设备一起使用多久。

输入格式

第一行给出两个整数 n , p n,p n,p

接下来 n n n 行,每行表示一个设备,给出两个整数,分别是这个设备的 a i a_i ai b i b_i bi

输出格式

如果小鸟可以无限使用这些设备,输出 − 1 -1 1

否则输出小鸟在其中一个设备能量降为 0 0 0 之前最多能使用多久。

设你的答案为 a a a,标准答案为 b b b,只有当 a , b a,b a,b 满足
∣ a − b ∣ max ⁡ ( 1 , b ) ≤ 1 0 − 4 \dfrac{|a-b|}{\max(1,b)} \leq 10^{-4} max(1,b)ab104 的时候,你能得到本测试点的满分。

样例 #1

样例输入 #1

2 1
2 2
2 1000

样例输出 #1

2.0000000000

样例 #2

样例输入 #2

1 100
1 1

样例输出 #2

-1

样例 #3

样例输入 #3

3 5
4 3
5 2
6 1

样例输出 #3

0.5000000000

提示

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 100000 1\leq n\leq 100000 1n100000 1 ≤ p ≤ 100000 1\leq p\leq 100000 1p100000 1 ≤ a i , b i ≤ 100000 1\leq a_i,b_i\leq100000 1ai,bi100000

解题思路

对于这道题思路肯定还是二分,我们发现这个二分的答案是小数,我们可以先二分出符合条件的最大整数x,然后再在x到x+1的范围内按照0.00001的精度去二分出我们想要的答案。
下一步就是check函数如何写的问题。给定我们一个时间,我们去判断设备能不能撑到这个时间。
思路就是,遍历每一个设备,如果设备本身的电量够用,则忽略,如果不够,则计算需要多少充电宝的电量,累加需要的充电宝的电量,最后如果不超过充电宝的电量,则可以撑到这个时间。

注意

1. 范围问题,一旦有溢出的可能,都要开longlong
2.答案的范围,先确定好答案的范围再去二分!!!

代码

#include<iostream>
using namespace std;

const int N = 1e5 +10;
int n,p;
int a[N],b[N];
long long suma;
long long sumb;
bool check1(long long mid){
    long long pro=p*mid;
    for(int i=1;i<=n;i++){
    	long long temp=a[i]*mid-b[i];
    	if(temp<0)temp=0;
		pro-=temp;
    	if(pro<0)return false;
	}
	return true;
}

bool check2(double mid){
    double pro=p*mid;
    for(int i=1;i<=n;i++){
    	double temp=a[i]*mid-b[i]*1.0;
    	if(temp<0.0)temp=0.0;
    	pro-=temp;
    	if(pro<0.0)return false;
	}
	return true;
}

//精度问题,但凡涉及超出范围的都要使用long long
//边界问题,一定要确定好的二分答案的边界

int main(){
    cin>>n>>p;
    for(int i=1;i<=n;i++){
        cin>>a[i]>>b[i];
        suma+=a[i];
        sumb+=b[i];
    }
    if(suma<=p){
        cout<<-1;
        return 0;
        
    }
    long long l=0,r=1e10;
    while(l<r){
        long long mid=l+r+1>>1;
        if(check1(mid)){
            l=mid;
        }else r=mid-1;
    }
    
    double ld=(double)l,rd=ld+1.0;
    while(rd-ld>0.000001){
    	double midd=(ld+rd)/2;
    	if(check2(midd)){
    		ld=midd;
		}else{
			rd=midd;
		}
	}
    cout<<ld;
}

题目描述

对于给定的一个长度为 N N N 的正整数数列 A 1 ∼ N A_{1\sim N} A1N,现要将其分成 M M M M ≤ N M\leq N MN)段,并要求每段连续,且每段和的最大值最小。

关于最大值最小:

例如一数列 4   2   4   5   1 4\ 2\ 4\ 5\ 1 4 2 4 5 1 要分成 3 3 3 段。

将其如下分段:

[ 4   2 ] [ 4   5 ] [ 1 ] [4\ 2][4\ 5][1] [4 2][4 5][1]

第一段和为 6 6 6,第 2 2 2 段和为 9 9 9,第 3 3 3 段和为 1 1 1,和最大值为 9 9 9

将其如下分段:

[ 4 ] [ 2   4 ] [ 5   1 ] [4][2\ 4][5\ 1] [4][2 4][5 1]

第一段和为 4 4 4,第 2 2 2 段和为 6 6 6,第 3 3 3 段和为 6 6 6,和最大值为 6 6 6

并且无论如何分段,最大值不会小于 6 6 6

所以可以得到要将数列 4   2   4   5   1 4\ 2\ 4\ 5\ 1 4 2 4 5 1 要分成 3 3 3 段,每段和的最大值最小为 6 6 6

输入格式

1 1 1 行包含两个正整数 N , M N,M N,M

2 2 2 行包含 N N N 个空格隔开的非负整数 A i A_i Ai,含义如题目所述。

输出格式

一个正整数,即每段和最大值最小为多少。

样例 #1

样例输入 #1

5 3
4 2 4 5 1

样例输出 #1

6

提示

对于 20 % 20\% 20% 的数据, N ≤ 10 N\leq 10 N10

对于 40 % 40\% 40% 的数据, N ≤ 1000 N\leq 1000 N1000

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 1 0 5 1\leq N\leq 10^5 1N105 M ≤ N M\leq N MN A i < 1 0 8 A_i < 10^8 Ai<108, 答案不超过 1 0 9 10^9 109

解题思路

和的最大值最小,二分右区间的最小值。核心依然是check函数怎么写。

注意

一定要合理的考虑答案的范围,最小的答案应该是数列中元素的最大值,最大的答案应该是数列的和

代码

#include<iostream>
using namespace std;

const int N = 1e5 +10;
int n,m;
int a[N];
long long pre[N];
int max_a;
bool check(long long mid){
    int sum=0;
    int cnt=0;
    for(int i=0;i<=n;i++){
        if(sum+a[i]<=mid){
            sum+=a[i];
            
        }
        else {
        	sum=a[i];
        	cnt++;
            if(cnt>m-1)return false;
		}
        
    }
    return true;
}

int main(){
    cin>>n>>m;
    
    for(int i=1;i<=n;i++){
        cin>>a[i];
        max_a=max(max_a,a[i]); 
    }
    //一定要合理的考虑答案的范围,最小的答案应该是数列的最大值,最大的答案应该是数列的和
    int l=max_a,r=1e8;
    while(l<r){
        long long mid = l+r>>1;
        if(check(mid))r=mid;
        else l=mid+1;
    }
    
    cout<<l;
}

题目背景

要保护环境

题目描述

木材厂有 n n n 根原木,现在想把这些木头切割成 k k k 段长度 l l l 的小段木头(木头有可能有剩余)。

当然,我们希望得到的小段木头越长越好,请求出 l l l 的最大值。

木头长度的单位是 cm \text{cm} cm,原木的长度都是正整数,我们要求切割得到的小段木头的长度也是正整数。

例如有两根原木长度分别为 11 11 11 21 21 21,要求切割成等长的 6 6 6 段,很明显能切割出来的小段木头长度最长为 5 5 5

输入格式

第一行是两个正整数 n , k n,k n,k,分别表示原木的数量,需要得到的小段的数量。

接下来 n n n 行,每行一个正整数 L i L_i Li,表示一根原木的长度。

输出格式

仅一行,即 l l l 的最大值。

如果连 1cm \text{1cm} 1cm 长的小段都切不出来,输出 0

样例 #1

样例输入 #1

3 7
232
124
456

样例输出 #1

114

提示

数据规模与约定

对于 100 % 100\% 100% 的数据,有 1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1n105 1 ≤ k ≤ 1 0 8 1\le k\le 10^8 1k108 1 ≤ L i ≤ 1 0 8 ( i ∈ [ 1 , n ] ) 1\le L_i\le 10^8(i\in[1,n]) 1Li108(i[1,n])

思路

常规题,比较简单,关键也是在于check函数,给定一个答案,判断这个答案是否可行

代码

#include<iostream>
#include<algorithm>
using namespace std;

const int N = 1e5 +10;
int n,k;
int a[N];

bool check(int mid){
    int num=0;
    for(int i=0;i<n;i++){
        num+=a[i]/mid;
        if(num>=k)return true;
    }
    return false;
}

int main(){
    cin>>n>>k;
    for(int i=0;i<n;i++){
        cin>>a[i];
    }
    int l=0,r=1e8;
    while(l<r){
        int mid = l+r+1>>1;
        if(check(mid))l=mid;
        else r=mid-1;
    }
    cout<<l;
}

题目描述

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

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N N N 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。

为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M M M 块岩石(不能移走起点和终点的岩石)。

输入格式

第一行包含三个整数 L , N , M L,N,M L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 L ≥ 1 L \geq 1 L1 N ≥ M ≥ 0 N \geq M \geq 0 NM0

接下来 N N N 行,每行一个整数,第 i i i 行的整数 D i   ( 0 < D i < L ) D_i\,( 0 < D_i < L) Di(0<Di<L), 表示第 i i i 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。

输出格式

一个整数,即最短跳跃距离的最大值。

样例 #1

样例输入 #1

25 5 2 
2
11
14
17 
21

样例输出 #1

4

提示

输入输出样例 1 说明

将与起点距离为 2 2 2 14 14 14 的两个岩石移走后,最短的跳跃距离为 4 4 4(从与起点距离 17 17 17 的岩石跳到距离 21 21 21 的岩石,或者从距离 21 21 21 的岩石跳到终点)。

数据规模与约定

对于 20 % 20\% 20%的数据, 0 ≤ M ≤ N ≤ 10 0 \le M \le N \le 10 0MN10
对于 50 % 50\% 50% 的数据, 0 ≤ M ≤ N ≤ 100 0 \le M \le N \le 100 0MN100
对于 100 % 100\% 100% 的数据, 0 ≤ M ≤ N ≤ 50000 , 1 ≤ L ≤ 1 0 9 0 \le M \le N \le 50000,1 \le L \le 10^9 0MN50000,1L109

解题思路

最短距离的最大值,仍然是二分。给定一个答案ans,判断答案是否可行。
check函数思路,遍历石头坐标,计算和上一个的距离,如果比ans小,那就移走这块石头(代码上只需要将其位置和上一块石头位置相等即可),同时cnt++,当cnt大于最多可以移走的数目,则不可行。
不要忘记判断起点和终点的石头
对于check函数操作原始数据时,我们最好是开辟一份拷贝数组,不要动原始数据

代码

#include<iostream>
#include<cstring>

using namespace std;

const int N =5e4 +10;
int q,n,m;
int d[N];
int a[N];
bool check(int mid){
    int cnt=0;
    //一定要拷贝一份数组进行操作,不然后面数据都出错了
    memcpy(a,d,sizeof(d));
    for(int i=1;i<=n;i++){
        if(a[i]-a[i-1]<mid){
            //把这个石头移走
            a[i]=a[i-1];
            cnt++;
            //cout<<i<<"移走\n"; 
            if(cnt>m){
            	//cout<<"不可\n";
            	return false;
			}
        }
        
    }
    //需要注意的是,最后一块石头到终点也是需要考虑的呢
    if(a[n+1]-a[n]<mid){
    	cnt++;
    	if(cnt>m)return false;
	}
    return true;
}

int main(){
    cin>>q>>n>>m;
    for(int i=1;i<=n;i++)cin>>d[i];
    d[n+1]=q;
    int l=1,r=q;
    while(l<r){
        int mid =l+r+1>>1;
        if(check(mid)){
            l=mid;
        }else r=mid-1;
        //cout<<mid<<"\n";
    }
	//check(7);
    cout<<l;
}

题目背景

B 市和 T 市之间有一条长长的高速公路,这条公路的某些地方设有路标,但是大家都感觉路标设得太少了,相邻两个路标之间往往隔着相当长的一段距离。为了便于研究这个问题,我们把公路上相邻路标的最大距离定义为该公路的“空旷指数”。

题目描述

现在政府决定在公路上增设一些路标,使得公路的“空旷指数”最小。他们请求你设计一个程序计算能达到的最小值是多少。请注意,公路的起点和终点保证已设有路标,公路的长度为整数,并且原有路标和新设路标都必须距起点整数个单位距离。

输入格式

1 1 1 行包括三个数 L , N , K L,N,K L,N,K,分别表示公路的长度,原有路标的数量,以及最多可增设的路标数量。

2 2 2 行包括递增排列的 N N N 个整数,分别表示原有的 N N N 个路标的位置。路标的位置用距起点的距离表示,且一定位于区间 [ 0 , L ] [0,L] [0,L] 内。

输出格式

输出 1 1 1 行,包含一个整数,表示增设路标后能达到的最小“空旷指数”值。

样例 #1

样例输入 #1

101 2 1
0 101

样例输出 #1

51

提示

公路原来只在起点和终点处有两个路标,现在允许新增一个路标,应该把新路标设在距起点 50 50 50 51 51 51 个单位距离处,这样能达到最小的空旷指数 51 51 51

50 % 50\% 50% 的数据中, 2 ≤ N ≤ 100 2 \leq N \leq 100 2N100 0 ≤ K ≤ 100 0 \leq K \leq 100 0K100

100 % 100\% 100% 的数据中, 2 ≤ N ≤ 100000 2 \leq N \leq 100000 2N100000, 0 ≤ K ≤ 100000 0 \leq K \leq100000 0K100000

100 % 100\% 100% 的数据中, 0 < L ≤ 10000000 0 < L \leq 10000000 0<L10000000

解题思路

这个题是最大距离的最小值,和上一个题还是蛮像的。整体是二分,核心仍然是check函数怎么写。
我们遍历数组,计算距离,如果距离大于mid,此时需要放路障,再当前路障上和上一个路障之间放置temp=(a[i]-a[i-1])/mid个(注意特殊情况,temp%mid==0时放temp-1个),如果最后放置路障的个数小于m,则可行。

代码

#include<iostream>
#include<cstring>

using namespace std;

const int N =1e5 +10;
int q,n,m;
int d[N];
int a[N];



bool check(int mid){
    int cnt=0;
    //一定要拷贝一份数组进行操作,不然后面数据都出错了
    //当然对这个题不需要,但是养成好习惯
    memcpy(a,d,sizeof(d));
    
    for(int i=1;i<n;i++){
        if(a[i]-a[i-1]>mid){
            //在中间加若干个路障以满足条件
            int temp=(a[i]-a[i-1])/mid;
            if((a[i]-a[i-1])%mid==0){
            	cnt+=temp-1; 
			}
			else cnt+=temp;
            
            //cout<<i<<"移走\n"; 
            if(cnt>m){
            	//cout<<"不可\n";
            	return false;
			}
			
        }
        
    }
    
    return true;
}

int main(){
    cin>>q>>n>>m;
    for(int i=0;i<n;i++){
        cin>>d[i];
        
    }
    int l=1,r=q;
    while(l<r){
        int mid =l+r>>1;
        if(check(mid)){
            r=mid;
        }else l=mid+1;
        //cout<<mid<<"\n";
    }
	//check(7);
    cout<<l;
}

二分刷题总结

总的来看,二分分为二分查找和二分答案,前者题目比较容易判断,对于后者而言,需要我们自己灵活判断,一般题目具有这样的特点。

  1. 让你找某个量的最大值或者最小值
  2. 题目正向思考答案不好想,但是如果给定一个答案可以判断它是否成立

这时可以考虑用二分来做

结语

行动是解决焦虑的最好办法!
请添加图片描述

  • 25
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值