【算法】二分答案 洛谷 LeetCode

二分答案

【参考:二分答案_哔哩哔哩_bilibili
【参考:P1182 数列分段’Section II’ - 2326 的博客 - 洛谷博客

把最值问题转化为判定问题

用二分的方法枚举答案,并且枚举时判断这个答案是否可行。

使用二分需要满足两个条件:

1.有上下界,即答案在一个固定的区间内

2.区间有单调性

凡是题目里出现要求“最大值最小”或“最小值最大”(或者是解读题意后发现),那么一般使用二分答案

重要地方在这个 最小最大

一定要理解题目意思,看看是求最大还是最小

check返回值要和二分if相对应,要分析并理解

最大值最小

  • P1182 数列分段 II 、P1083 借教室、蓝桥杯 打包

最小值最大:

  • P2440 木材加工、P1577 切绳子、 P2678 跳石头、P1873 砍树、P1824 进击的奶牛

其他情况

  • P1024 一元三次方程求解

模板

最小值最大
mid尽量靠右边,即low=mid+1 -> mid会变大

// 分段间隔mid 分段数num 题目要求的分段数y
boolean check(int mid){
   ...
    return num >= y;
    // 大于时 说明 num满足要求,因为是求最大值,所以要尽可能变大一点 进入low = mid + 1;
    // 等于 时进入low = mid + 1;,因为是求最大值
    // 小于时 说明 num不满足要求,要使num变大一点,即分段数要多点,即mid要小一点 进入 high = mid - 1;
}


int low = 最小可能的值; 
int high = 最大可能的值;

int result= 不存在的值
while (low <= high) {
      int mid = (low + high) / 2;
      if (check(mid)) {
          result=mid;
          low = mid + 1; // 注意这里
      } else {
          high = mid - 1;
      }
}
输出result

最大值最小
mid尽量靠左边,即high=mid-1 => mid会变小

记得理解,不要死记硬背


boolean check(int mid){
   ...
    return num <= y;
    // 等于 时进入high=mid-1;,因为是求最小值
}


int low = 最小可能的值; 
int high = 最大可能的值;

int result= 不存在的值
while (low <= high) {
      int mid = (low + high) / 2;
      if (check(mid)) {
          result=mid;
          high = mid - 1; // 注意这里
      } else {
          low = mid + 1;
      }
}
输出result

浮点数

在这里插入图片描述
在这里插入图片描述

  • P1577 切绳子

P2440 木材加工

1.有上下界
[low,high] 1,木头的最长值

2.区间有单调性
[low,high] 1->木头的最长值

【参考:P2440 木材加工 - 洛谷 | 计算机科学教育新生态

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

即求小段木头的最大值

import java.io.*;


public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter out = new PrintWriter(System.out);

        String s1 = in.readLine();
        String[] s1_arr = s1.split(" ");
        int N = Integer.parseInt(s1_arr[0]);
        int k = Integer.parseInt(s1_arr[1]);

        int[] arr = new int[N];

        int low = 1; // 小段木头最小的长度
        int high = Integer.MIN_VALUE; // // 小段木头最大的值即为最长的木棍的长度max(arr)
        for (int i = 0; i < N; i++) {
            arr[i] = Integer.parseInt(in.readLine());
            high = Math.max(high, arr[i]);
        }


        int result = 0;// 结果 如果连 1cm 长的小段都切不出来,输出 0
        // 在最小值和最大值之间使用二分法遍历 时间复杂度O(logn) 区间[low..mid..high]
        while (low <= high) {
            int mid = (low + high) / 2;
            if (check(mid, arr, k)) {
                result = mid; // 记录 小段木头的长度
                // 分段数 > 指定分段次数 ,说明指定的小段木头的长度太小,应该让小段木头的长度大点,才能让分段次数少点,因此进入右区间low = mid + 1

                // 分段数 = 指定分段次数 , 此时分段数刚刚好,得继续往右区间看有没有更大的值满足要求,因此low = mid + 1
                // 此时mid的左边都是(分段数 < 指定分段次数),mid是左区间的最大值,再尝试往右走一下看看能不能找到一个满足条件且更大的
                low = mid + 1;
            } else {
                // 分段数 < 指定分段次数 , 说明此时指定的和太大,导致分段数太少,因此要让小段木头的长度小一点,因此进入左区间,high = mid -1
                high = mid - 1;
            }
        }
        // 总的时间复杂度 O(nlogn)
        out.println(result);
        out.flush();

    }

    // 当小段木头的长度的最大值为mid时,判断是否能分成k段 时间复杂度O(n)
    public static boolean check(int mid, int[] arr, int k) {
        int sum = 0;// 分段数
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i] / mid;// 每根棍子能分割成几根长度为mid的棍子
        }
        return sum >= k;
        /*
        分析:
		  如果 sum > k 则说明mid低了,需要大一点,即向右靠 low = mid + 1;
		  如果 sum < k 则说明mid高了,需要小一点,即向左靠 high = mid - 1;
		  如果 sum == k 则说明mid正好,此时mid为候选答案,记录下来.
		  由于是求最大值,所以需要要往右边再看看有没有更大的满足要求,如果不满足则就是这个mid,如果可以则继续,
		  即向右靠 low = mid + 1;
		  综上所述 sum >= k 对应 low = mid + 1;

		 */
    }

}

P1577 切绳子

求最小长度的绳子的最大值

【参考:P1577 切绳子 - 洛谷 | 计算机科学教育新生态

import java.io.*;


public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter out = new PrintWriter(System.out);

        String s1 = in.readLine();
        String[] s1_arr = s1.split(" ");
        int N = Integer.parseInt(s1_arr[0]);
        int k = Integer.parseInt(s1_arr[1]);

        int[] arr = new int[N];

        int low = 1; // 小段绳子最小的长度
        int high = Integer.MIN_VALUE; // // 小段绳子最大的值即为最长的绳子的长度max(arr)
        for (int i = 0; i < N; i++) {
            double x = Double.parseDouble(in.readLine());
            arr[i]=(int) (x*100);
            high = Math.max(high, arr[i]);
        }


        int result = 0;// 结果 
        // 在最小值和最大值之间使用二分法遍历 时间复杂度O(logn) 区间[low..mid..high]
        while (low <= high) {
            int mid = (low + high) / 2;
            if (check(mid, arr, k)) {
                result = mid; // 记录 小段绳子的长度
                // 分段数 > 指定分段次数 ,说明指定的小段绳子的长度太小,应该让小段绳子的长度大点,才能让分段次数少点,因此进入右区间low = mid + 1

                // 分段数 = 指定分段次数 , 此时分段数刚刚好,得继续往右区间看有没有更大的值满足要求,因此low = mid + 1
                // 此时mid的左边都是(分段数 < 指定分段次数),mid是左区间的最大值,再尝试往右走一下看看能不能找到一个满足条件且更大的
                low = mid + 1;
            } else {
                // 分段数 < 指定分段次数 , 说明此时指定的和太大,导致分段数太少,因此要让小段绳子的长度小一点,因此进入左区间,high = mid -1
                high = mid - 1;
            }
        }
        // 总的时间复杂度 O(nlogn)
        out.printf("%.2f",(double)result / 100);
        out.flush();

    }

    // 当小段绳子的长度的最大值为mid时,判断是否能分成k段 时间复杂度O(n)
    public static boolean check(int mid, int[] arr, int k) {
        int sum = 0;// 分段数
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i] / mid;// 每根棍子能分割成几根长度为mid的棍子
        }
        return sum >= k;
    }

}

P2678 跳石头

【参考:P2678 [NOIP2015 提高组] 跳石头 - 洛谷 | 计算机科学教育新生态

【参考:二分答案算法超详细教程_汪阿少的博客-CSDN博客

1.有上下界
[low,high] 1,起点到终点的距离

2.区间有单调性
[low,high] 1->起点到终点的距离

求最短跳跃距离的最大值

import java.io.*;


public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter out = new PrintWriter(System.out);

        String s1 = in.readLine();
        String[] s1_arr = s1.split(" ");
        int L = Integer.parseInt(s1_arr[0]);
        int N = Integer.parseInt(s1_arr[1]);
        int M = Integer.parseInt(s1_arr[2]);

        // 组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石 所以起点0,中间N,终点N+1
        int[] arr = new int[N+2]; //n是终点前的最后一块石头,所以arr[n+1]=l,记录终点的值
        // arr[0]=0;
        arr[N+1]=L;

        int low = 1; // 最短跳跃距离的最小值
        int high = L; // // 最短跳跃距离的最大值 起点和终点的距离
        // 表示第 i 块岩石与起点的距离
        for (int i = 1; i <= N; i++) {
            arr[i] = Integer.parseInt(in.readLine().trim());
        }
        
        int result = 0;// 结果
        // 在最小值和最大值之间使用二分法遍历 时间复杂度O(logn) 区间[low..mid..high]
        while (low <= high) {
            int mid = (low + high) / 2;
            if (check(mid, arr, M)) {
                result = mid; // 记录 最短跳跃距离的长度
                // 分段数 > 指定分段次数 ,说明指定的最短跳跃距离的长度太小,应该让最短跳跃距离的长度大点,才能让分段次数少点,因此进入右区间low = mid + 1

                // 分段数 = 指定分段次数 , 此时分段数刚刚好,得继续往右区间看有没有更大的值满足要求,因此low = mid + 1
                // 此时mid的左边都是(分段数 < 指定分段次数)满足要求的,mid是左区间的最大值,再尝试往右走一下看看能不能找到一个满足条件且更大的
                low = mid + 1;
            } else {
                // 分段数 < 指定分段次数 , 说明此时指定的最短跳跃距离太大,导致分段数太少,因此要让最短跳跃距离的长度小一点,因此进入左区间,high = mid -1
                high = mid - 1;
            }
        }
        // 总的时间复杂度 O(nlogn)
        out.println(result);
        out.flush();

    }

    // 当至多移走M个石头,最短跳跃距离的长度的最大值为mid时,判断是否可行 时间复杂度O(n)
    public static boolean check(int mid, int[] arr, int M) {
        int sum = 0;//需要移动的总数
        int now=0;// 当前石头所在的位置
        // 这里要和初始化对应
        for (int i = 1; i < arr.length; i++) {
            //如果当前石头now与下一块石头i之间的距离比我们设定的最短的距离要小, 那么这块石头就得移走.
            if((arr[i]-arr[now])<mid){
                sum++;// 移走该石头i
            }else{
                now=i;// 更新当前石头所在的位置
            }
        }
        return sum <= M;
    }

}

P1873 砍树

【参考:P1873 [COCI 2011/2012 #5] EKO / 砍树 - 洛谷 | 计算机科学教育新生态

Mirko 非常关注生态保护,所以他不会砍掉过多的木材。这也是他尽可能高地设定伐木机锯片的原因。请帮助 Mirko 找到伐木机锯片的最大的整数高度 HH,使得他能得到的木材至少为 M 米(只能多,不能少)。换句话说,如果再升高 1 米,他将得不到 M 米木材。

即伐木机锯片高度的为x时,正好得到M米木材,x再大就不行了,即求即伐木机锯片高度的最大值


import java.io.*;

public class Main {

    public static void main(String[] args) throws IOException {
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
        in.nextToken();
        int N =(int)in.nval; //  in.nval 默认返回 double
        in.nextToken();
        int M =(int)in.nval; //  in.nval 默认返回 double


        int[] a = new int[N];

        int high = Integer.MIN_VALUE; // 最大值为 树的最高高度
        int  low= Integer.MAX_VALUE; // 最小值为 树的最低高度

        for (int i = 0; i < N; i++) {
            in.nextToken();
            a[i] = (int)in.nval;
            low= Math.min(low, a[i]);
            high = Math.max(high, a[i]);
        }

        int result = 0;// 结果
        // 在最小和和最大和之间使用二分法遍历 时间复杂度O(logn) 区间[low..mid..high]
        while (low <= high) {
            int mid = (low + high) >> 1;
            if (check(mid, a, M)) {
                result = mid;
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        out.print(result);
        out.close();

    }

    // 当伐木机锯片的最大的整数高度为mid时,判断是否至少能收集到 M米木材 时间复杂度O(n)
    public static boolean check(int mid, int[] arr, int M) {
        int sum = 0;// 收集到木材的总长度
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] > mid) {
                sum += arr[i] - mid;
                // 不加下面这行就有三个用例不过 ???
                if (sum >= M)
                    return true; // 满足条件提前返回
            }
        }
        return sum >= M;
        /*
		  分析:
		  如果 sum > m 则说明mid低了,需要大一点,即向右靠 low = mid + 1;
		  如果 sum < m 则说明mid高了,需要小一点,即向左靠 high = mid - 1;
		  如果 sum == m 则说明mid正好,此时mid为候选答案,记录下来.
		  由于是求最大值,所以需要要往右边再看看有没有更大的满足要求,如果不满足则就是这个mid,如果可以则继续,
		  即向右靠 low = mid + 1;
		  综上所述 sum >= M 对应 low = mid + 1;
		 */
    }

}

P1824 进击的奶牛

【参考:P1824 进击的奶牛 - 洛谷 | 计算机科学教育新生态

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

public class Main {
    static int n;
    static int c;
    static int[] arr;

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        c = sc.nextInt();

        arr = new int[n];
        int low = 1;
        int high = Integer.MIN_VALUE;
        for (int i = 0; i < n; i++) {
            arr[i] = sc.nextInt();
            high = Math.max(high, arr[i]);
        }
        Arrays.sort(arr); // 排序

        int result = 0;
        while (low <= high) {
            int mid = (low + high) >> 1;
            if (check(mid)) {
                result = mid;
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        System.out.println(result);

    }

    // 间隔距离为mid
    static boolean check(int mid) {
        int num = 1;// 能放几头牛
        int last = arr[0];// 上一头牛的安置坐标
        for (int i = 1; i < n; i++) {
            if (arr[i]-last < mid) {
                continue; // 两头牛的间隔小于mid
            } else {
                last = arr[i];
                num++;
            }

        }
        return num >= c; // 等于的时候恰好可以,因为求最大值 所有 low要尽可能大
    }


}

—间隔符—

P1182 数列分段 II

1.有上下界
[low,high] 数组最大的数,数组的和

2.区间有单调性
[low,high] 数组最大的数 -> 数组的和

求所有最大值字段和中的最小值

【参考:P1182 数列分段 Section II - 洛谷 | 计算机科学教育新生态

【参考:第1部分 基础算法–第2章 二分与三分1436:数列分段II_zqhf123的博客-CSDN博客

import java.io.*;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter out = new PrintWriter(System.out);

        String s1 = in.readLine();
        String[] s1_arr = s1.split(" ");
        int N = Integer.parseInt(s1_arr[0]);
        int M = Integer.parseInt(s1_arr[1]);

        int[] a = new int[N];

        String s2 = in.readLine();
        String[] s2_arr = s2.split(" ");

        int high = 0; // 最大分段的和:初始状态只分一段,此时最大和为sum(arr)
        int low = Integer.MIN_VALUE; // 最小的和:全部都分段,此时最小和为max(arr)
        for (int i = 0; i < s2_arr.length; i++) {
            a[i] = Integer.parseInt(s2_arr[i]);
            high += a[i];
            low = Math.max(low, a[i]);
        }


        int result=0;// 结果
        // 在最小和和最大和之间使用二分法遍历 时间复杂度O(logn) 区间[low..mid..high]
        while (low <= high) {
            int mid = (low + high) >> 1;
            if (check(mid, a, M)) {
                // 分段数 = 指定分段次数 , 此时分段数刚刚好,得继续往左区间看有没有更小的子段和满足要求,因此high = mid -1
                // 此时mid的右边都是(分段数 < 指定分段次数),mid是右区间的最小值,再尝试往左走一下看看能不能找到一个满足条件且更小的
                result=mid;
                // 分段数 < 指定分段次数 , 说明此时指定的和太大,导致分段数太少,因此要让和小一点,因此进入左区间,high = mid -1
                high = mid - 1;

            } else {
                // 分段数 > 指定分段次数 ,说明指定的和太小,应该让和大点,才能让分段次数少点,因此进入右区间low = mid + 1
                low = mid + 1;
            }
        }
        // 总的时间复杂度 O(nlogn)
        out.println(result); 
        out.flush();

    }

    // 当分段和最大值为mid时,判断是否能分成M段 时间复杂度O(n)
    public static boolean check(int mid, int[] arr, int M) {
        int sum = 0;// 分段的和,这里从arr[0]开始第一段
        int cnt = 1; // 分段数,至少为1段,即本身
        for (int i = 0; i < arr.length; i++) {
            if ((sum + arr[i]) <= mid) { // 该分段和小于或等于mid
                sum += arr[i];
            } else {
                sum = arr[i]; // 该分段和大于mid,重新从arr[i]开始分下一段
                cnt++;// 分段数++
            }
        }
        return cnt <= M; 
        /*
          分析:
          如果段数 cnt > m ,说明这个mid小了,它还可以再大一点 low = mid + 1;
          如果段数 cnt < m , 说明这个mid大了,那么它就要小一点了,high = mid - 1;
          如果段数 cnt == m 由于此时cnt可能等于m,这个mid为候选答案,记录下来.由于是求最小值,所以要往左边再看看,high = mid - 1;
          如果不满足则答案就是这个mid,如果可以则继续
          此时mid的右边都是(分段数 < 指定分段次数),mid是右区间的最小值,
          再尝试往左走一下看看能不能找到一个满足条件且更小的 high = mid - 1;
          综上所述 cnt <= m对应 high = mid - 1;
         */
    }

}


【参考:SaikrVj | 1.4.4 数列分段
在这里插入图片描述
high += a[i]; 可能会越界

所以设置一个数据可能的最大值 1 0 9 10^9 109

int high = 1000000000;

去掉 high += a[i];

【参考:P1182 【数列分段 Section II】Python题解 - Jezemy 的博客 - 洛谷博客

5 3
4 2 4 5 1 

l=5,h=16,m=10
4,2,4 5,1 两段,不符合要求,分段数少了,说明m大了,h=m-1=9

l=5,h=9,m=7
4,2 4 5,1 三段,符合要求,分段数刚刚好,再往左区间看看有没有更小的 h=m-1=6

l=5,h=6,m=5
4 2 4 5 1 五段,不符合要求,分段数多了,说明m小了,l=m+1=6

l=6,h=6,m=6
4,2 4 5,1 三段,符合要求,分段数刚刚好,再往左区间看看有没有更小的 h=m-1=5

退出while

import java.io.*;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter out = new PrintWriter(System.out);

        String s1 = in.readLine();
        String[] s1_arr = s1.split(" ");
        int N = Integer.parseInt(s1_arr[0]);
        int M = Integer.parseInt(s1_arr[1]);

        int[] a = new int[N];

        String s2 = in.readLine();
        String[] s2_arr = s2.split(" ");

        int high = 0; // 最大分段的和:初始状态只分一段,此时最大和为sum(arr)
        int low = Integer.MIN_VALUE; // 最小的和:全部都分段,此时最小和为max(arr)
        for (int i = 0; i < s2_arr.length; i++) {
            a[i] = Integer.parseInt(s2_arr[i]);
            high += a[i];
            low = Math.max(low, a[i]);
        }


        // int result=0;// 结果
        // 在最小和和最大和之间使用二分法遍历 时间复杂度O(logn) 区间[low..mid..high]
        while (low <= high) {
            int mid = (low + high) / 2;
            if (check(mid, a, M)) {
                // 分段数 > 指定分段次数 ,说明指定的和太小,应该让和大点,才能让分段次数少点,因此进入右区间low = mid + 1
                low = mid + 1;
            } else {
                // 分段数 = 指定分段次数 , 此时分段数刚刚好,得继续往左区间看有没有更小的子段和满足要求,因此high = mid -1
                // 此时mid的右边都是(分段数 < 指定分段次数),mid是右区间的最小值,再尝试往左走一下看看能不能找到一个满足条件且更小的

                // 分段数 < 指定分段次数 , 说明此时指定的和太大,导致分段数太少,因此要让和小一点,因此进入左区间,high = mid -1
                high = mid - 1;
            }
        }
        // 总的时间复杂度 O(nlogn)
        out.println(low); // ???
        out.flush();

    }

    // 当分段和最大值为maxSum时,判断是否能分成M段 时间复杂度O(n)
    public static boolean check(int maxSum, int[] arr, int M) {
        int sum = 0;// 分段的和,这里从arr[0]开始第一段
        int cnt = 1; // 分段数,至少为1段,即本身
        for (int i = 0; i < arr.length; i++) {
            if ((sum + arr[i]) <= maxSum) { // 该分段和小于或等于maxSum
                sum += arr[i];
            } else {
                sum = arr[i]; // 该分段和大于maxSum,重新从arr[i]开始分下一段
                cnt++;// 分段数++
            }
        }
        return cnt > M; // 不能分成M段,返回false,否则返回true
    }

}

P1083 借教室

【参考:P1083 [NOIP2012 提高组] 借教室 - 洛谷 | 计算机科学教育新生态

每张订单其实就可以看作是一个区间(操作),左右区间分别为开始时间和结束时间,所以这不就是一个区间操作吗 =》 差分数组

有上下界,序列满足单调性
如果订单号mid不能满足,此时mid为候选答案,如果[0,mid]里面可能有比mid还早的订单不能满足,那么继续向左靠直到找到最先不能满足的那个订单号 =》二分答案

题目:按照订单的先后顺序依次为每份订单分配教室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配

如果一个人无法被满足,则他后面的人全都不能被满足;如果一个人可以被满足,则他前面的人都可以被满足。这恰恰吻合了我们二分的性质。

【参考:题解 P1083 【借教室】 - ShawnZhou 的博客 - 洛谷博客

import java.io.*;
import java.util.*;

public class Main {
    static int n;
    static int m;
    static int[] line;// 第i天可用于租借的教室数量
    static long[] need;// 第i天需要租借的教室数量
    // static int[] s;
    // static int[] t;
    // static int[] d;
    static long[] change;//差分数组


    public static void main(String[] args) throws IOException {
        // BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter out = new PrintWriter(System.out);
        StreamTokenizer sc = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        sc.nextToken();
        n = (int) sc.nval;
        sc.nextToken();
        m = (int) sc.nval;

        // String s1 = in.readLine();
        // String[] arr1 = s1.split(" ");
        // n = Integer.parseInt(arr1[0]);
        // m = Integer.parseInt(arr1[1]);


        line = new int[n + 1];
        need = new long[n + 2];
        change = new long[n + 2];
        // 天数与订单均用从 1 开始的整数编号 ,所以这里也从下标1开始存储数据
        int[] d = new int[m + 1];
        int[] s = new int[m + 1];
        int[] t = new int[m + 1];


        // String s2 = in.readLine();
        // String[] arr2 = s2.split(" ");
        // for (int i = 0; i < n; i++) {
        //     line[i + 1] = Integer.parseInt(arr2[i]);
        // }

        for (int i = 0; i < n; i++) {
            sc.nextToken();
            line[i + 1] = (int) sc.nval;
        }

        // String s3;
        // String[] arr3;
        // for (int i = 0; i < m; i++) {
        //     s3 = in.readLine().trim();
        //     arr3 = s3.split(" ");
        //     d[i + 1] = Integer.parseInt(arr3[0]);
        //     s[i + 1] = Integer.parseInt(arr3[1]);
        //     t[i + 1] = Integer.parseInt(arr3[2]);
        // }

        for (int i = 0; i < m; i++) {
            sc.nextToken();
            d[i + 1] = (int) sc.nval;
            sc.nextToken();
            s[i + 1] = (int) sc.nval;
            sc.nextToken();
            t[i + 1] = (int) sc.nval;
        }

        //obj 4什么情况是全都可以成立
        // 检查前m个订单是否满足
        if (check(m, d, s, t)) {
            out.println(0);
            out.flush();
            return;
        }

        int low = 1;
        int high = m; // 最大的订单号

        // 二分答案最小值问题
        int mid;
        int result = 0;
        while (low <= high) {
            mid = (low + high) >> 1;
            if (check(mid, d, s, t)) {
                low = mid + 1;
            } else {
                result = mid;
                high = mid - 1;
            }
        }

        out.println(-1);
        out.println(result);
        out.flush();
    }

    // need:[1,mid]之间的需要房间总数,即前x个订单,所以每次都要置0
    // 判断能不能满足前x个订单
    public static boolean check(int x, int[] d, int[] s, int[] t) {
        Arrays.fill(need, 0);// 每次都要置0
        Arrays.fill(change, 0);// 每次都要置0
        // 因为need都为0,所以change初始化也全为0 change[i]=need[i]-need[i-1]

        // // 差分数组变化
        //obj 1用差分数组对前x个订单操作进行处理
        for (int i = 1; i <= x; i++) {
            change[s[i]] += d[i]; // 这里题目最大给了10^9 3个10^9相加就越界了,所以要用int
            change[t[i] + 1] -= d[i];
        }

        // 复原序列need
        //obj 2抹平差分数组
        for (int i = 1; i <= n; i++) {
            need[i] = need[i - 1] + change[i];
            if (need[i] > line[i]) // 无法满足
                return false;
        }


        // //obj 3 与上面合并到一起
        // for (int i = 1; i <= n; i++)
        //     if (need[i] > line[i]) // 无法满足
        //         return false;
        return true;
    }


}

蓝桥杯 打包

【参考:“蓝桥杯”练习系统试题 算法提高 打包

Lazy有N个礼物需要打成M个包裹,邮寄给M个人,这些礼物虽然很便宜,但是很重。Lazy希望每个人得到的礼物的编号都是连续的。为了避免支付高昂的超重费,他还希望让包裹的最大重量最小。

让包裹的最大重量最小

import java.util.*;


public class Main {
    static int n;
    static int m;
    static int[] arr;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        arr = new int[n];

        int low = Integer.MIN_VALUE;
        int high = 0;

        for (int i = 0; i < n; i++) {
            arr[i] = sc.nextInt();
            low = Math.max(low, arr[i]); // 最小值应该是 数组中最大的那个
            high += arr[i]; // 最大值应该是 数组的和
        }

        int result = 0;// 结果
        while (low <= high) {
            int mid = (low + high) >> 1;

            if (check(mid)) { 
                result = mid;
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }

        System.out.println(result);
    }


    static boolean check(int mid) {
        int num = 1; // 包裹数 至少为一个包裹 
        // 如果这里初始为0,那么下面for里面的if就要修改 这样的写比较麻烦
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            if (sum + arr[i] <= mid) {
                sum += arr[i]; // 该包裹重量和小于或等于mid
            } else {
                sum = arr[i]; // 该包裹重量和大于mid,重新从arr[i]开始分下一段
                num++;
            }
        }
        return num <= m; // 等于 时进入high=mid-1;,因为是求最小值

    }


}

—间隔符—

P1024 一元三次方程求解

【参考:P1024 [NOIP2001 提高组] 一元三次方程求解 - 洛谷 | 计算机科学教育新生态
在这里插入图片描述

第二个测试点的数据
1 -4.65 2.25 1.4
-0.35 1.00 4.00 
import java.util.Scanner;

public class Main {
    static double a, b, c, d;
    static double eps = 1e-4;

    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 low = i, high = i + 1; // 处理区间[low,high)上的根 左闭右开 防止端点处是零点导致得到重复解

            if (f(low) ==0) { // 如果 low 是根,则直接输出
                System.out.printf("%.2f ", low);
            }
//            else if (f(high) ==0) { // 如果 high 是根,则跳过 其实这句可要可不要
//                continue;
//            }
            else if (f(low) * f(high) < 0) { // 在 low和high中有根 进行二分
                double result=0;
                while (high - low > eps) { // 二者间隔大于eps  二分控制精度
                    double mid = (low + high) / 2;
                    if (f(mid) * f(high) > 0) { // 如果f(mid) 和 f(high)正负性相同,则零点在mid左侧
                        high = mid;
                        
                    } else {
                        // 零点在mid右侧
                        result=mid; // 经测试result只能放这,放上面就不行?? 或者if和else两处都放也可以
                        low = mid;
                    }
                }
                System.out.printf("%.2f ", low);// 这里输出low high都可以  result也可以,不过要放在else那里或者两处都放
            }
        }
    }

    static double f(double x) {
//        System.out.println(a * x * x * x + b * x * x + c * x + d);
        return a * x * x * x + b * x * x + c * x + d;
    }


}

其实这道题暴力枚举两位小数也能过

Leetcode中等

875. 爱吃香蕉的珂珂

【参考:875. 爱吃香蕉的珂珂 - 力扣(LeetCode)

class Solution {

    public int minEatingSpeed(int[] piles, int h) {
        int low=1; // 这里注意,速度的最小值是 1,不是piles数组的最小值
        int high=Integer.MIN_VALUE;
        for(int i=0;i<piles.length;i++){
            high=Math.max(high,piles[i]);
        }
        int result=0;
        while(low<=high){
            int mid=low+(high-low)/2;
            if(check(mid,h,piles)){
                result = mid;
                high = mid - 1;
            }else{
                low = mid + 1;
            }
        }

        return result;
    }
    // k最大值最小
    public boolean check(int k,int h,int[] piles){
        int res=0;
        for(int i=0;i<piles.length;i++){
            if(piles[i]<=k){
                res++;
            }else if(piles[i]%k==0){
                res+=piles[i]/k;
            }else{
                res+=piles[i]/k;
                res++;
            }
        }
        return res<=h; // 等于时进入high = mid - 1;,继续找更小的
    }
}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值