洛谷题单 算法1-6 二分查找与二分答案

kotori的设备

题目背景
kotori 有 n 个可同时使用的设备。

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

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

kotori 想把这些设备一起使用,直到其中有设备能量降为 0。所以 kotori 想知道,

在充电器的作用下,她最多能将这些设备一起使用多久。

输入格式
第一行给出两个整数 n,p。

接下来 n 行,每行表示一个设备,给出两个整数,分别是这个设备的ai 和 bi。

输出格式
如果 kotori 可以无限使用这些设备,输出-1。

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

设你的答案为 a,标准答案为 b,只有当 a,b 满足 的时候,你能得到本测

试点的满分。

输入输出样例
输入 #1复制
2 1
2 2
2 1000
输出 #1复制
2.0000000000
输入 #2复制
1 100
1 1
输出 #2复制
-1
输入 #3复制
3 5
4 3
5 2
6 1
输出 #3复制
0.5000000000
说明/提示
对于 100%的数据, 1<=n<=100000,1<=p<=100000,1<=ai,bi<=100000。

思路:题目要求的是最多可以使用的时间,我们可以先假设一个最大的使用时间,然后将此时间带入计算,看一下每台设备初始储存的能量是否足够使用这些时间,如果足够的话,我们不需要对他充电,而如果不足够的话,我们要对他进行充电,使他足够支撑这些时间。最后比较一下在这个时间内需要充电的电量是否大于在此时间内充电宝所能提供的最大电能。如果大于的话说明我们假设的这个时间不符合要求,应该向下假设。看到数据范围,暴力枚举肯定会超时,所以我们采用二分,需要注意的是对于小数的二分,我们视两个端点均为开区间取不到。



import java.util.Scanner;

public class Main{
    static long sum;
    static int n,p;
    static double mid,left,right=1e10;
    static int[] a = new int[100005],b = new int[100005];
    public static void main(String[] args) {
        Scanner sc= new Scanner (System.in);
        n = sc.nextInt();
        p = sc.nextInt();
        for(int i=0;i<n;i++){
            a[i] = sc.nextInt();
            b[i] = sc.nextInt();
            sum+=a[i];
        }
        if(sum<=p){
            System.out.println("-1");
            System.exit(0);
        }
        while(right-left>1e-6){
            mid = (left+right)/2;
            if(ok(mid)){
                left = mid ;
            }else {
                right = mid;
            }
        }
        System.out.printf("%.10f",left);
    }
    public static boolean ok(double x){
        double sum = 0;
        for(int i=0;i<n;i++){
            if(a[i]*x<=b[i]){
                continue;
            }else {
                sum +=(a[i]*x-b[i]);
            }
        }
        return p*x>=sum;
    }
}

跳石头

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

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

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

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

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

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

输入输出样例
输入 #1复制
25 5 2
2
11
14
17
21
输出 #1复制
4
思路:我们寻找最短的跳跃距离,暴力枚举肯定是不行的,我们可以对最短跳跃距离进行二分答案。寻找符合条件的最大最短跳跃距离。怎么判断是不是符合条件呢?我们可以采用模拟的方法,每次判断当前站立的位置与下一块石头间的距离是否小于我们二分出来的mid,如果小于,那么我们将它移走。为什么?因为mid是我们找的最短的跳跃距离,它比这个最短距离还小,那么我们肯定要移走它。注意,大于等于的时候就不要移走它了。我们统计一下移走石头的数量,如果数量大于m,那么便是不符合条件的。因为我们只能移走m块,显然达不到这个最短距离。



import java.util.Arrays;
import java.util.Scanner;

public class Main{
    static long count;
    static int l,n,m,max;
    static int[] a;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        l = sc.nextInt();
        n = sc.nextInt();
        m = sc.nextInt();
        a = new int[n+2];
        for(int i=1;i<=n;i++){
            a[i] = sc.nextInt();
        }
        a[n+1] = l;
        int mid ;
        int left = 1;
        int right = l+1;
        while(left<right){
            mid = (right+left)/2;
            if(ok(mid)){//符合条件
                left = mid+1;
            }else {
                right = mid;
            }
        }
        System.out.println(left-1);
    }
    public static boolean ok(int x){
        int t0 = 0; //当前站立在的位置
        int t1 = 0;//我们要判断的石头
        int count = 0;//计数器 统计移走了多少块石头
        while(t1<=n){
            t1++;
            if(a[t1]-a[t0]<x){
                count++;
            }else{
                t0 = t1;
            }
        }
        if(count>m){
            return false;
        }else {
            return true;
        }
    }
}

[TJOI2007]路标设置

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

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

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

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

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

输入输出样例
输入 #1复制
101 2 1
0 101
输出 #1复制
51
说明/提示
公路原来只在起点和终点处有两个路标,现在允许新增一个路标,应该把新路标设在距起点50或51个单位距离处,这样能达到最小的空旷指数51。

思路;和上一个跳石头思路相似,只需要对代码进行一点修改即可。



import java.util.Arrays;
import java.util.Scanner;

public class Main{
    static long count;
    static int l,n,m,k,max;
    static int[] a;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        l = sc.nextInt();
        n = sc.nextInt();
        k = sc.nextInt();
        a = new int[n+1];
        for(int i=1;i<=n;i++){
            a[i] = sc.nextInt();
        }
        int mid ;
        int left = 1;
        int right = l+1;
        while(left<right){
            mid = (right+left)/2;
            if(ok(mid)){//符合条件
                right = mid;
            }else {
                left = mid+1;
            }
        }
        System.out.println(right);
    }
    public static boolean ok(int x){
        int t0 = 0; //当前站立的位置距离起点的距离
        int t1 = 2;//我们要判断的路标
        int count = 0;//计数器 统计插了几块路标
        while(t1<=n){
            if(a[t1]-t0>x){
                t0+=x;
                count++;
            }else{
                t0 = a[t1];
                t1++;
            }
        }
        if(count>k){
            return false;
        }else {
            return true;
        }
    }
}

数列分段 Section II

题目描述
对于给定的一个长度为N的正整数数列 A1∼AN ,现要将其分成 M(M≤N)段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:
例如一数列 4 2 4 5 1 要分成 3 段。
将其如下分段:
[4 2][4 5][1]
第一段和为 6,第 2 段和为 9,第 3 段和为 1,和最大值为 9。
将其如下分段:
[4][2 4][5 1]
第一段和为 4,第 2 段和为 6,第 3 段和为 6,和最大值为 6。
且无论如何分段,最大值不会小于 6。
所以可以得到要将数列 4 2 4 5 1 要分成 3 段,每段和的最大值最小为 6。
输入格式
第 1 行包含两个正整数 N,M。
第 2 行包含 N 个空格隔开的非负整数 Ai,含义如题目所述。

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

输入输出样例
输入 #1复制
5 3
4 2 4 5 1
输出 #1复制
6
说明/提示
对于20% 的数据,N≤10。

对于 40% 的数据,N≤1000。

对于 100% 的数据,1≤N≤10^5,M≤N,Ai < 10^8, 答案不超过 10^9。

思路:首先明确的是,题目让我们找的是分组后每组和的最大值的最小值。
第一个问题,我们应该怎么为数组分组呢? 其实我们可以通过先假设一个最大值的方式来判断这种情况下要分成几组才能使每组和都不大于我们假设的那个数。如果组数大于m组的话,很明显不符合题意,我们需要将假设的数值变大。而假设的值我们可以通过二分的方式来获得。
第二个问题,二分的初始区间是什么呢?因为题目给的是所有数均为正整数,所以左区间我们设为数组中最大元素的值,右区间设为所有元素的和的值。然后开始二分。



import java.util.Arrays;
import java.util.Scanner;

public class Main{
    static long count;
    static int l,n,m,k,max,left,right;
    static int[] a = new int[100005];
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        for(int i=0;i<n;i++){
            a[i] = sc.nextInt();
            left = Math.max(left,a[i]);
            right += a[i];
        }
        right++;
        while(left<right){
            int mid = (right+left)/2;
            if(ok(mid)){
                right = mid;
            }else {
                left = mid+1;
            }
        }
        System.out.println(right);
    }
    public static boolean ok(int x){ //判断是否每组都小于x
        int sum = 0;
        int count = 1;
        for(int i=0;i<n;i++){
            if(sum+a[i]<=x){
                sum+=a[i];
            }else {
                sum = a[i];
                count++;
            }
        }
        return count<=m;
    }
}

A-B 数对

题目描述
出题是一件痛苦的事情!

相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!

好吧,题目是这样的:给出一串数以及一个数字 C,要求计算出所有A−B=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。

输入格式
输入共两行。

第一行,两个整数N,C。

第二行,N 个整数,作为要求处理的那串数。

输出格式
一行,表示该串数中包含的满足 A−B=C 的数对的个数。

输入输出样例
输入 #1复制
4 1
1 1 2 3
输出 #1复制
3
说明/提示
对于 75% 的数据,1≤N≤2000。
对于 100% 的数据,1≤N≤2×10^5。
保证所有输入数据都在 3232 位带符号整数范围内。

思路:题单说的是二分查找 but 用java写了一个二分查找,只能过8组数据,有两组数据是过不了的,所以换个思路。寻找a-b=c数对的个数,就是寻找满足b=a-c的b的个数。既然二分会超时,那么我们可以把每个数及每个数出现的次数储存在map集合中,每次在集合中通过b获得b出现此数即可。此思路可以ac。



import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main{
    static int[] a;
    static long ans;
    static int n,c;
    static Map<Integer,Integer> map =new HashMap<>();
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        c = sc.nextInt();
        a = new int[n];
        for(int i=0;i<n;i++){
            a[i] = sc.nextInt();
            if(map.containsKey(a[i])){
                map.put(a[i],map.get(a[i])+1);
            }else{
                map.put(a[i],1);
            }
        }
        Arrays.sort(a);
        for(int i=0;i<n;i++){
            int t1 = a[i];
            int t2 = t1 -c;
            if(t2==t1){
                ans+=map.get(t2)-1;
            }else{
                if(map.containsKey(t2))
                ans+=map.get(t2);
            }
        }
        System.out.print(ans);
    }
}

题目描述
伐木工人米尔科需要砍倒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个整数,表示砍树的最高高度。

输入输出样例
输入 #1复制
5 20
4 42 40 26 46
输出 #1复制
36

思路:调多高呢?暴力枚举显然是不行的,很自然的想到二分查找。我们通过寻找右边界获得答案。



import java.util.Scanner;

public class Main{
    static long n,m;
    static long[] a ;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextLong();
        m = sc.nextLong();
        a = new long[(int)n];
        for(int i=0;i<n;i++){
            a[i] = sc.nextLong();
        }
        System.out.println(bin());
    }
    public static long bin(){
        long mid = 0;
        long left = 1;
        long right = 2000000000+1;
        while(left<right){
            mid = (left+right)/2;
            if(ok(mid)){
                left = mid+1;
            }else{
                right = mid;
            }
        }
        return right-1;
    }
    public static boolean ok(long x){ //判断以x为高度是否符合条件
        long sum = 0;
        for(int i=0;i<n;i++){
            if(a[i]>x){
                sum += a[i]-x;
            }
        }
        return sum>=m;
    }
}

【深基13.例1】查找

题目描述
输入 n(n≤10^6 ) 个不超过 10^9 的单调不减的(就是后面的数字不小于前面的数字)非负整数 a1 ,a2 ,…,an ,然后进行m(m≤10^5 ) 次询问。对于每次询问,给出一个整数 q(q≤10^9 ),要求输出这个数字在序列中的编号,如果没有找到的话输出 -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

思路:水题,,二分查找,但是用java和c,相同的代码,一个超时了,一个ac了,我也是日了狗了

#include
int n,q,m;
int a[1000005];
int bin(int q){
	int head = 0;
	int  tail = n;
	int flag = 0;
	while(head<tail){
		int mid=(head+tail)/2;
		if(a[mid]==q){
			flag = 1;
			tail = mid;
		}else if(a[mid]>q){
			tail=mid;
		}else{
			head = mid+1;
		}
	}
	if(flag){
		return tail;
	}else{
		return -2;
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++){
		scanf("%d",&a[i]);
	} 
	for(int i=0;i<m;i++){
		scanf("%d",&q);
		printf("%d ",bin(q)+1); 
	} 
	return 0;
} 

一元三次方程求解

题目描述
有形如:ax^3+ bx^2 + cx^1 +dx^0=0,这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d均为实数),并约定该方程存在三个不同实根(根的范围在−100至100之间),且根与根之差的绝对值≥1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后2位。

提示:记方程f(x)=0,若存在2个数x1 和x2 ,且x1

输入格式
一行,4个实数A,B,C,D。

输出格式
一行,3个实根,并精确到小数点后2位。

输入输出样例
输入 #1复制
1 -5 -4 20
输出 #1复制
-2.00 2.00 5.00

思路:题目说了,两个根差的绝对值不小于1,也就是说在长度为1的(左闭右开)区间里只能有一个根,题目又给了判断根存在的条件。我们可以从-100到100枚举长度为1的区间,如果满足fun(left)*fun(right)<0,那么在此区间里肯定存在实根,我们可以在此区间中二分查找。需要特别注意的是,计算结果为double类型,当两数差的绝对值小于0.01时我们认为两数相等。


import java.util.Scanner;

public class Main{
    static double a,b,c,d,x1,x2;
    static int count;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        a = sc.nextDouble();
        b = sc.nextDouble();
        c = sc.nextDouble();
        d = sc.nextDouble();
        for(int i=-100;i<=100;i++){
            double left = i;         //左区间
            double right = i+1.00; //右区间
            if(Math.abs(fun(left)-0)<0.01){
                System.out.printf("%.2f ",left);
                count++;
            }
            if(fun(left)*fun(right)<0.0){ //区间里存在根
                while(right-left>0.001){ //查找根的值
                    double mid = (left+right)/2;
                    if(Math.abs(fun(mid)-0)<0.01){
                        System.out.printf("%.2f ",mid);
                        count++;
                        break;
                    } else if(fun(mid)*fun(right)<0){
                        left = mid+0.01;
                    }else if(fun(mid)*fun(right)>0){
                        right = mid;
                    }
                }
            }
            if(count==3){
                break;
            }
        }
    }
    public static double fun(double x){
        return a*Math.pow(x,3)+b*Math.pow(x,2)+c*Math.pow(x,1)+d;
    }
}

烦恼的高考志愿

题目背景
计算机竞赛小组的神牛V神终于结束了万恶的高考,然而作为班长的他还不能闲下来,班主任老t给了他一个艰巨的任务:帮同学找出最合理的大学填报方案。可是v神太忙了,身后还有一群小姑娘等着和他约会,于是他想到了同为计算机竞赛小组的你,请你帮他完成这个艰巨的任务。

题目描述
现有m(m≤100000) 所学校,每所学校预计分数线是ai(ai ≤10^6 )。有 n(n≤100000) 位学生,估分分别为 bi (bi ≤10^6)。
根据n位学生的估分情况,分别给每位学生推荐一所学校,要求学校的预计分数线和学生的估分相差最小(可高可低,毕竟是估分嘛),这个最小值为不满意度。求所有学生不满意度和的最小值。

输入格式
第一行读入两个整数m,n。m表示学校数,n表示学生数。第二行共有m个数,表示m个学校的预计录取分数。第三行有n个数,表示n个学生的估分成绩。

输出格式
一行,为最小的不满度之和。

输入输出样例
输入 #1复制
4 3
513 598 567 689
500 600 550
输出 #1复制
32
思路:肯定不能暴力,会超时,很容易想到二分查找,二分查找的什么呢?先将a数组排序,我们找b[i]应该插入在a数组中的位置,刚好a中有元素等于b[i],那么我们返回0就可以,对于a[0]>=b[i]或者a[m-1]<=b[i]我们可以特判一下。



import java.util.Arrays;
import java.util.Scanner;

public class Main{
    static long ans;
    static int count,n,m;
    static int[] a,b;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        m = sc.nextInt();
        n = sc.nextInt();
        a = new int[m];
        b = new int[n];
        for(int i=0;i<m;i++){
            a[i] = sc.nextInt();
        }
        Arrays.sort(a);
        for(int i=0;i<n;i++){
            b[i] = sc.nextInt();
        }
        for(int i=0;i<n;i++){
            ans+=bin(b[i]);
        }
        System.out.println(ans);
    }
    public static int bin(int x){//查找x应该插在a数组中的位置
        if(a[0]>=x){
            return a[0]-x;
        }else if(a[m-1]<=x){
            return x-a[m-1];
        }
        int mid = 0;
        int left = 0;
        int right = m;
        while(left<right){
            mid = (left+right)/2;
            if(a[mid]==x){
                return 0;
            }else if(a[mid]>x){
                right = mid;
            }else{
                left = mid+1;
            }
        }
        int t0 = Math.abs(a[mid]-x);
        if(mid-1>=0){
            int t1 = Math.abs(a[mid-1]-x);
            t0 = Math.min(t1,t0);
        }if(mid+1<m){
            int t2 = Math.abs(a[mid+1]-x);
            t0 = Math.min(t2,t0);
        }
        return t0;
    }
}

木材加工

题目背景
要保护环境
题目描述
木材厂有一些原木,现在想把这些木头切割成一些长度相同的小段木头(木头有可能有剩余),需要得到的小段的数目是给定的。当然,我们希望得到的小段木头越长越好,你的任务是计算能够得到的小段木头的最大长度。木头长度的单位是cm。原木的长度都是正整数,我们要求切割得到的小段木头的长度也是正整数。

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

输入格式
第一行是两个正整数N和K(1 ≤ N ≤ 100000,1 ≤ K ≤ 100000000),N是原木的数目,K是需要得到的小段的数目。

接下来的N行,每行有一个1到100000000之间的正整数,表示一根原木的长度。

输出格式
能够切割得到的小段的最大长度。如果连1cm长的小段都切不出来,输出”0”。

输入输出样例
输入 #1复制
3 7
232
124
456
输出 #1复制
114

思路:蓝桥杯考过一道类似的,好像是分巧克力的。这题很明显,暴力会超时,我们采用二分查找。查找范围为1~max,其中max为所有木头中最长的长度。特判一下,如果所有木头加起来的长度还小于,那么我们输出0。



import java.util.Arrays;
import java.util.Scanner;

public class Main{
    static long count;
    static int n,k,max;
    static int[] a;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        k = sc.nextInt();
        a = new int[n];
        for(int i=0;i<n;i++){
            a[i] = sc.nextInt();
            count+=a[i];
            max = Math.max(max,a[i]);
        }
        if(count<k){
            System.out.println("0");
            System.exit(0);
        }else if(k%count==0){
            System.out.println(k/count);
            System.exit(0);
        }
        System.out.println(bin());
    }
    public static int bin(){
        int mid ;
        int left = 1;
        int right = max+1;
        while(left<right){
            mid = (left+right)/2;
            if(sum(mid)>=k){
                left = mid+1;
            }else{
                right = mid;
            }
        }
        return right-1;
    }
    public static long sum(int x){
        long s = 0;
        for(int i=0;i<n;i++){
            s+=a[i]/x;
        }
        return s;
    }
}

你可能感兴趣的:(算法初探)