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

P2249 【深基13.例1】查找

解题思路

思路简单,设计二分查找算法,注意判断未查找到输出-1的情况。数据较大需要引入io流读写。具体看代码。

import java.io.*;

public class Main {
    // 快读快写
    static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));

    public static void main(String[] args) throws IOException {
        int n = read();
        int m = read();
        // 数字数组开大点,否则会出现RE
        int[] a = new int[n + 2];

        for (int i = 0; i < n; i++) {
            a[i] = read();
        }

        for (int i = 0; i < m; i++) {
            int b = read();
            pw.print(find(a, b) + " ");
        }
        pw.flush();
    }

    public static int read() throws IOException {
        st.nextToken();
        return (int) st.nval;
    }


    public static int find(int[] arr, int n) {
        // 这里arr接收的a数组,长度是n + 2,所以这里right是 -3
        int left = 0, right = arr.length - 3;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            if (arr[mid] >= n) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        if (arr[left] == n) {
            // 数组索引从0开始,输出结果从1开始,所以 +1
            return left + 1;
        }else return -1;
    }
}

P1102 A-B 数对

解题思路

方法有很多,这里的解题思路只讲我个人理解,每个方法都有相应的解释。

核心思想是将A-B=C转换成A-C=B。这样将A、B两个变元切换到A-C一个变元,再将结果与数组中的B对照记录即可。

哈希表法 

首先将A数组每个元素出现的次数统计起来,用map映射,最后将A数组每次减一个C,再将A数组扫一遍,将所有映射的次数和加起来就是答案

 m.getOrDefault(a[i], 0L)是一个方法调用,表示从映射m中获取键为a[i]的值,如果键不存在,则返回默认值0L

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

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        int n = input.nextInt();
        long c = input.nextLong();
        long[] a = new long[200001];
        // 创建一个HashMap来存储每个元素的出现频率
        Map<Long, Long> m = new HashMap<>();
        // 将两个变量转化为一个变量,遍历数组中的每个元素减去c
        // A-B=C --> A-C=B

        for (int i = 1; i <= n; i++) {
            a[i] = input.nextLong();
            // 更新当前元素的频率映射
            m.put(a[i], m.getOrDefault(a[i], 0L) + 1);
            // 从当前元素中减去c并将其存储回数组中
            a[i] -= c;
        }

        long ans = 0;

        for (int i = 1; i <= n; i++) {
            // 将当前元素的频率(在减法之后)加到结果中
            ans += m.getOrDefault(a[i], 0L);
        }

        System.out.println(ans);
    }
}

二分查找法

如果这个数组是有序的,那么对于每一个A的值,在它的后方就只有一个数值B满足A-B=C。

这时我们只需要使用两个函数求出数组中对于每个A,A+C的这两个位置,它们的差即为数组中数值为A+C的元素个数。将这个数加到ans中。

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

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        int n = input.nextInt();
        long c = input.nextLong();
        long[] a = new long[200001];
        long ans = 0;

        // 读取数组的元素
        for (int i = 1; i <= n; i++) {
            a[i] = input.nextLong();
        }

        // 对数组进行排序
        Arrays.sort(a, 1, n + 1);

        // 遍历数组计算满足条件的对数
        for (int i = 1; i <= n; i++) {
            ans += upperBound(a, 1, n + 1, a[i] + c) - lowerBound(a, 1, n + 1, a[i] + c);
        }

        // 输出结果
        System.out.println(ans);
    }

    // 查找大于或等于 key 的第一个位置
    public static int lowerBound(long[] array, int fromIndex, int toIndex, long key) {
        int low = fromIndex;
        int high = toIndex;
        while (low < high) {
            int mid = (low + high) / 2;
            if (array[mid] < key) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return low;
    }

    // 查找大于 key 的第一个位置
    public static int upperBound(long[] array, int fromIndex, int toIndex, long key) {
        int low = fromIndex;
        int high = toIndex;
        while (low < high) {
            int mid = (low + high) / 2;
            if (array[mid] <= key) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return low;
    }
}

双指针法

我们考虑题目要求求出所有A-B=C的数对,我们可以先将原数组排序,然后就会发现每个数A,对应的数B一定是一段连续的区间。

然后我们再考虑如何去找到这个区间。

我们显然是要找到这个连续区间的左端点和右端点。

考虑到排序之后序列的有序性,我们枚举每个数,他们的左端点和右端点都是单调不降的,因此我们可以用双指针来维护这个东西。

具体的实现是:我们维护两个右端点r1 , r2,每次r1右移到a[r1] - a[l] <= c的最后位置的下一位,r2右移到满足a[r2] - a[l] < c最后一位.

也就是说, 此时如果a[r2] - a[l] == c && a[r1 - 1] - a[l] == c,中间的那一段一定都是满足条件的,我们让ans += r1 - r2即可。

 该题解来自双指针法题解

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

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        
        int n = input.nextInt();
        int c = input.nextInt();
        int[] a = new int[200001];

        // 读取数组的元素
        for (int i = 1; i <= n; i++) {
            a[i] = input.nextInt();
        }

        // 对数组进行排序
        Arrays.sort(a, 1, n + 1);

        // 初始化双指针和结果变量
        int l, r1 = 1, r2 = 1;
        long ans = 0;

        // 遍历数组计算满足条件的对数
        for (l = 1; l <= n; l++) {
            // 移动 r1 指针找到第一个大于 a[l] + c 的位置
            while (r1 <= n && a[r1] - a[l] <= c) r1++;
            // 移动 r2 指针找到第一个大于等于 a[l] + c 的位置
            while (r2 <= n && a[r2] - a[l] < c) r2++;
            // 检查条件是否满足,如果满足则更新结果
            if (r2 <= n && a[r2] - a[l] == c && a[r1 - 1] - a[l] == c && r1 - 1 >= 1) {
                ans += r1 - r2;
            }
        }

        System.out.println(ans);
    }
}

P1873 [COCI 2011/2012 #5] EKO / 砍树 

解题思路

 二分查找

在高度范围内查找到刚好使得sum<m的数即为答案

 该代码在java8可以获得90,实例3需要多跑几次才能AC;在java21,

该代码会出现内存超出或者超时的情况,如果要用java21,需要调用快读快写的类。

例如之前的第一题题解中用到的st和pw

import java.util.Scanner;  

public class Main {  
    public static void main(String[] args) {  
        Scanner input = new Scanner(System.in);  

        int n = input.nextInt();  
        long m = input.nextInt();  
        
        int[] a = new int[n];  
        int max = 0;  

        // 读取n个整数到数组中,并确定数组中的最大值  
        for (int i = 0; i < n; i++) {  
            a[i] = input.nextInt();  
            if (a[i] >= max) {  
                max = a[i];
            }  
        }  

        // 使用二分搜索来找到最大的高度(left)  
        // 使得砍掉这个高度以上的树木将至少获得m单位木材  
        int left = 0, right = max;  
        while (left + 1 != right) {  
            int mid = (left + right) / 2;
            
            // 检查在中间高度(mid)砍伐是否能够获得至少m单位木材  
            if (check(mid, a, m)) {  
                left = mid; // 如果可以,向上移动左边界  
            } else {  
                right = mid; // 如果不可以,向下移动右边界  
            }  
        }  
        
        System.out.println(left);  
    }  

    // 函数检查在特定高度mid是否能够产生至少m单位木材  
    private static boolean check(int mid, int[] a, long m) {  
        long sum = 0; // 初始化木材总量  
        for (int j : a) {  
            if (mid < j) { // 只考虑比mid高的树  
                sum += (j - mid); // 计算砍掉部分获得的多余木材  
            }  
        }  
        return sum >= m; // 如果总木材量大于或等于m,返回true  
    }  
}

P1024 [NOIP2001 提高组] 一元三次方程求解 

解题思路

方法一:高中导数知识+牛顿迭代法

可以对该方程求导,令导函数为0,求出极大值和极小值。题目保证有3个解,那么说明该函数存在两个峰值,根的分布就在(-\propto,极大(小)值),(极大(小)值,极小(大)值),(极小(大)值,+\propto)。

import java.util.Scanner;

public class Main {
    static double a;
    static double b;
    static double c;
    static double d;
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        a = input.nextDouble();
        b = input.nextDouble();
        c = input.nextDouble();
        d = input.nextDouble();

        double x = Math.pow(b, 2) - 3 * a * c;
        double p = (-b - Math.sqrt(x)) / (3 * a);
        double q = (-b + Math.sqrt(x)) / (3 * a);

        // 牛顿法求解三个根
        double x1 = func(-100, p);
        double x2 = func(p, q);
        double x3 = func(q, 100);

        System.out.printf("%.2f %.2f %.2f", x1, x2, x3);

    }

    // 牛顿法求解方程f(x)=0的根
    public static double func(double left, double right) {
        // 需要找的根
        double x;
        // 初始化中值
        double mid = (left + right) / 2;

        // 循环直到收敛
        do {
            // 应用牛顿法公式
            x = mid - f(mid) / df(mid);
            // 检查是否达到收敛条件
            if (Math.abs(x - mid) < 0.00001) {
                break;
            }
            // 更新mid的值
            mid = x;
        }while (true);
        return x;
    }

    // 定义三次多项式函数
    public static double f(double x) {
        return a * Math.pow(x, 3) + b * Math.pow(x, 2) + c * x + d;
    }

    // 定义三次多项式的导函数
    public static double df(double x) {
        return 3 * a * Math.pow(x, 2) + 2 * b * x + c;
    }
}

方法二:一元三次方程求根公式

简单粗暴,但是公式复杂,思路与求解一元二次方程一致。

由于公式过于庞杂且无深究必要,这里就不贴代码了,具体证明参考解一元三次方程推到公式过程

 方法三:暴力枚举

由于解的范围在[-100,100]之间,数据较小,且确保存在三个实数解,直接for循环枚举即可。

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        double a = input.nextDouble();
        double b = input.nextDouble();
        double c = input.nextDouble();
        double d = input.nextDouble();

        for (double i = -100; i <= 100; i += 0.001) {
            // 避免double精度错误
            if (Math.abs(a * i * i * i + b * i * i + c * i + d) < 0.0001) {
                System.out.printf("%.2f ", i);
            }
        }
    }
}

方法四:二分法

根据题目给出的提示,在[-100,100]内用二分法查找零点即可

import java.util.Scanner;

public class Main {
    static double a, b, c, d;

    public static double f(double x) {
        return a * x * x * x + b * x * x + c * x + d;
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        a = input.nextDouble();
        b = input.nextDouble();
        c = input.nextDouble();
        d = input.nextDouble();

        double left, right, mid, x1, x2;
        // 记录根的个数
        int count = 0;

        for (int i = -100; i < 100; i++) {
            left = i;
            right = i + 1;
            x1 = f(left);
            x2 = f(right);

            // 如果left对应的值为0,说明left为一个零点
            // 不检查right是因为会造成重复
            if (x1 == 0) {
                System.out.printf("%.2f ", left);
                count++;
            }

            // 根据提示,如果两个解的乘积<0,说明存在零点
            if (x1 * x2 < 0) {
                // 二分查找
                while (right - left >= 0.001) {
                    mid = (left + right) / 2;
                    if (f(mid) * f(right) <= 0)
                        left = mid;
                    else
                        right = mid;
                }
                System.out.printf("%.2f ", right);
                count++;
            }

            // 找出三个根就退出循环
            if (count == 3)
                break;
        }

    }
}

P1678 烦恼的高考志愿

解题思路

贪心

  • 排序

    • 将学校的预计分数线数组 a 进行排序。
    • 将学生的估分数组 b 进行排序。
  • 匹配

    • 对于每个学生,从学校分数线中找到最接近的分数线,计算他们之间的差值,并累加所有差值即为总的不满意度。
import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        int m = input.nextInt();
        int n = input.nextInt();

        // 读入 m 个学校的预计录取分数
        int[] a = new int[m];
        for (int i = 0; i < m; i++) {
            a[i] = input.nextInt();
        }

        // 读入 n 个学生的估分
        int[] b = new int[n];
        for (int i = 0; i < n; i++) {
            b[i] = input.nextInt();
        }

        // 排序
        Arrays.sort(a);
        Arrays.sort(b);

        // 计算最小不满度
        long min = 0;
        int index = 0;

        for (int i = 0; i < n; i++) {
            // 找到最近的学校分数线
            while (index < m - 1 && Math.abs(a[index] - b[i]) >= Math.abs(a[index + 1] - b[i])) {
                index++;
            }
            min += Math.abs(a[index] - b[i]);
        }

        // 输出最小不满度之和
        System.out.println(min);
    }
}

 二分查找

  • 读取输入

    • 首先读取学校数 m 和学生数 n
    • 接着读取 m 个学校的预计分数线并存储在数组 a 中。
    • 然后对数组 a 进行排序,以便后续快速查找最近的分数线。
  • 遍历学生的估分

    • 对于每个学生的估分 b,使用 Arrays.binarySearch 在排序后的数组 a 中查找 b 的位置。如果 b 不在数组中,binarySearch 返回一个负数,通过 -e - 1 计算出插入点 e
  • 计算不满意度

    • 如果插入点 e 等于数组长度 m,说明 b 比所有学校分数线都高,因此选择最高的分数线 a[m-1] 计算不满意度。
    • 如果插入点 e 为0,说明 b 比所有学校分数线都低,因此选择最低的分数线 a[0] 计算不满意度。
    • 否则,b 在数组中间,计算 b 与插入点 e 和前一个位置 e-1 的分数线的绝对差值,取最小值作为不满意度。
  • 累加不满意度

    • 对每个学生计算得到的不满意度进行累加,最后输出总的不满意度之和。
import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        int m = input.nextInt();
        int n = input.nextInt();

        int[] a = new int[m];
        for (int i = 0; i < m; i++) {
            a[i] = input.nextInt();
        }

        Arrays.sort(a);

        long ans = 0; // 用于存储最终的不满意度之和

        // 遍历每个学生的估分
        for (int i = 0; i < n; i++) {
            int b = input.nextInt();
            // 使用二分查找找到估分b在排序后的数组a中的位置
            int e = Arrays.binarySearch(a, b);

            // 如果没有找到,binarySearch返回一个负数,计算插入点
            if (e < 0) {
                e = -e - 1;
            }

            // 如果插入点是数组的末尾,说明b比所有分数都高
            if (e == m) {
                ans += b - a[m - 1];
            } 
            // 如果插入点是数组的开头,说明b比所有分数都低
            else if (e == 0) {
                ans += a[0] - b;
            } 
            // 如果在数组中间,计算最近的两个分数的绝对差值的最小值
            else {
                ans += Math.min(Math.abs(a[e] - b), Math.abs(b - a[e - 1]));
            }
        }

        System.out.println(ans);
    }
}

P2440 木材加工

解题思路

在限定范围之内使用二分法查找可行的长度。看注释。

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        int n = input.nextInt();
        int k = input.nextInt();
        int[] l = new int[n];

        for (int i = 0; i < n; i++) {
            l[i] = input.nextInt();
        }

        // 二分查找初始化范围,left表示最短长度,right表示最长长度+1
        int left = 0, right = 100000001;

        // 二分查找
        while (left + 1 < right) {
            int mid = (left + right) / 2;
            // 如果长度为mid时,可以切出至少k根木棒,说明mid长度可能有效
            if (func(n, k, mid, l)) {
                left = mid; // 尝试更长的长度
            } else {
                right = mid; // 缩小范围
            }
        }
        // 输出最大长度
        System.out.println(left);
    }

    // 判断在给定长度mid的情况下,能否切出至少k根木棒
    public static boolean func(int n, int k, int mid, int[] a) {
        int ans = 0;
        for (int i = 0; i < n; i++) {
            ans += a[i] / mid; // 计算每根木棒能切出的长度为mid的木棒数量
        }
        return ans >= k; // 判断总数是否大于等于k
    }
}

P2678 [NOIP2015 提高组] 跳石头

解题思路 

  • 初始化数据

    • 从输入中读取线段的长度 l、石头的数量 n 和最多可以移动的石头数量 m
    • 将石头的位置存储在一个数组 dis 中,并在数组末尾添加终点 l,使得 dis[n+1] = l
  • 二分搜索的范围设置

    • 我们的跳跃距离范围是从 0 到 l + 1(终点后的一个位置)。left 初始化为 0right 初始化为 l + 1
  • 二分搜索的核心

    • 在每次迭代中,计算中点 mid,表示当前尝试的最大跳跃距离。
    • 设定一个变量 sum 用于记录当跳跃距离为 mid 时需要移动多少个石头,x 是当前位置的指针。
  • 计算移动的石头数量

    • 遍历所有石头位置(从第一个到最后一个)。
    • 如果当前位置石头到上一个"保持"石头的位置的距离小于 mid,那么需要移动这个石头,sum 增加。
    • 否则,更新 x 到当前石头的位置,开始新的跳跃。
  • 调整搜索区间

    • 如果计算出的 sum 小于或等于 m,说明可以允许更大的跳跃,因此将 left 更新为 mid
    • 如果需要移动的石头数超过了 m,则说明当前跳跃距离 mid 太大,更新 right 为 mid
  • 终止条件

    • 当 left + 1 >= right 时,搜索结束,最后的 left 就是满足条件的最大跳跃距离。
import java.util.Scanner;

public class Main {

    static int l, n, m;
    static int[] rock = new int[50001];

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        l = input.nextInt();
        n = input.nextInt();
        m = input.nextInt();

        for (int i = 1; i <= n; i++) {
            rock[i] = input.nextInt();
        }
        rock[n + 1] = l;//添加端点

        int left = 0, right = l + 1; // 二分搜索的初始边界
        while (left + 1 < right) {
            int mid = (left + right) / 2;
            int count = 0;
            int start = rock[0]; // 起始跳跃位置初始化为0

            for (int i = 1; i <= n + 1; i++) { // 遍历距离
                if (rock[i] - start < mid) {
                    count++; // 如果跳跃距离小于中间,则必须移动一块石头
                } else {
                    start = rock[i]; // 更新跳跃位置
                }
            }

            if (count <= m) {
                left = mid; // 如果要移动的石头数量在限制范围内,我们可以尝试更大的距离
            } else {
                right = mid; // 如果需要移动的石头太多,减小距离
            }
        }

        System.out.println(left);

    }
}

P3853 [TJOI2007] 路标设置

解题思路

基本思路和上题“跳石头”相似

关键点:每次找到一个新的空旷指数时,我们都遍历一遍 arr 数组,如果有一次间隔 temp 比新的空旷指数大,则我们需要增添 \frac{temp-1}{mid}个路标。最后再看路标是否超过了k,以此判断该动 left 还是 right。

import java.util.Scanner;

public class Main {
    static int l, n, k;
    static int[] arr = new int[100001];

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        l = input.nextInt();
        n = input.nextInt();
        k = input.nextInt();

        for (int i = 0; i < n; i++) {
            arr[i] = input.nextInt();
        }

        // left,right表示二分查找的左右边界,ans表示最终答案
        int left = 1, right = 10000000, ans = 0;
        while (left <= right) {
            int mid = (left + right) / 2;
            // 查找符合条件的路标个数
            int max = 0;
            for (int i = 1; i < n; i++) {
                int temp = arr[i] - arr[i - 1];
                if (temp > mid) {
                    max += (temp - 1) / mid;
                }
            }
            // 如果最大值小于等于k,说明mid可以满足条件,更新ans,并将右边界缩小
            if (max <= k) {
                ans = mid;
                right = mid - 1;
            }else {
                // 如果最大值大于k,说明mid不能满足条件,将左边界增大,寻找更小的答案
                left = mid + 1;
            }
        }
        System.out.println(ans);
    }
}

P1182 数列分段 Section II

解题思路

  • 确定问题本质:
    • 给定一个数组,将其分成 M 段,使得每段和的最大值最小。
  • 初始化二分查找范围:
    • 最小值是数组中元素的最大值(因为每段至少要包含一个元素)。
    • 最大值是数组所有元素的和(这是将整个数组作为一段的情况)。
  • 二分查找过程:
    • 在范围 [left, right] 内二分查找,通过计算中间值 mid 判断是否可以将数组分成 M 段,并且每段的和不超过 mid
    • 如果可以分,则 mid 可能是一个解,将范围缩小到 [left, mid]
    • 如果不可以分,则 mid 太小,需要尝试更大的值,将范围调整到 [mid + 1, right]
  • 判断是否可以分段的贪心算法:
    • 初始化当前段的和为0,段数为1。
    • 遍历数组,累加当前段的和,如果超过 mid,则重开一个新的段,并将段数加1。
    • 如果段数超过 M,则说明 mid 太小。
import java.util.Scanner;

public class Main {

    // 判断是否可以将数组A分成不超过M段,并且每段的和不超过maxSum
    private static boolean canSplit(int[] arr, int m, int maxSum) {
        int currentSum = 0;
        int requiredParts = 1; // 初始化段数为1

        for (int num : arr) {
            // 如果当前段的和加上新的元素超过了maxSum,则重新开始一段
            if (currentSum + num > maxSum) {
                requiredParts++;
                currentSum = num;

                // 如果段数超过了M,则无法在maxSum限制下分成M段
                if (requiredParts > m) {
                    return false;
                }
            } else {
                currentSum += num;
            }
        }
        return true;
    }

    // 使用二分查找来确定每段和的最大值的最小可能值
    public static int minimizeMaxSegmentSum(int m, int[] arr) {
        int left = 0, right = 0;

        // 初始化二分查找的范围
        for (int num : arr) {
            left = Math.max(left, num); // 左边界是数组中的最大值
            right += num; // 右边界是数组元素之和
        }

        while (left < right) {
            int mid = (left + right) / 2;

            // 判断mid是否可以作为最大段和
            if (canSplit(arr, m, mid)) {
                right = mid; // mid是可能的解,尝试更小的值
            } else {
                left = mid + 1; // mid太小,尝试更大的值
            }
        }
        return left; // 最小的最大段和
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        // 读取输入
        int n = input.nextInt();
        int m = input.nextInt();
        int[] arr = new int[n];

        for (int i = 0; i < n; i++) {
            arr[i] = input.nextInt();
        }

        // 计算并输出结果
        int result = minimizeMaxSegmentSum(m, arr);
        System.out.println(result);

    }
}

P1163 银行贷款

解题思路

根据题意,公式如下:

w_{0}\times (1+r)^{m}=w\times \frac{(1+r)^{m}-1}{r}

  • w_0 是贷款的初始值
  • w 是每月支付的金额
  • m 是支付的月数
  • r 是月利率

我们需要求解 r。由于这是一个非线性方程,我们可以使用二分查找的方法来逼近 r

步骤:

  1. 定义求解范围:根据题意,月利率 r 是一个百分数,答案不超过 300%,所以我们可以在 0%300% 的范围内进行二分查找。

  2. 实现二分查找:通过二分查找,找到一个合适的月利率,使得按照这个利率计算出来的分期付款金额接近输入的每月支付金额 w

  3. 四舍五入:最后将结果四舍五入到小数点后1位。

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        // 读取输入
        int w0 = input.nextInt();  // 贷款的初始值
        int w = input.nextInt();   // 每月支付的金额
        int m = input.nextInt();   // 支付的月数

        // 计算月利率
        double result = func(w0, w, m);

        // 输出结果,四舍五入到1位小数
        System.out.printf("%.1f\n", result);
    }

    public static double func(int w0, int w, int m) {
        // 定义二分查找的范围
        double low = 0.0;
        double high = 300.0;

        // 设置精度
        double epsilon = 1e-8;

        while (high - low > epsilon) {
            double mid = (low + high) / 2;
            double r = mid / 100.0; // 将百分数转化为小数

            // 计算根据当前利率的月供金额
            double calculatedW = w0 * r * Math.pow(1 + r, m) / (Math.pow(1 + r, m) - 1);

            // 二分查找调整上下界
            if (calculatedW < w) {
                low = mid;
            } else {
                high = mid;
            }
        }

        // 返回结果,取中间值并四舍五入到1位小数
        return (low + high) / 2;
    }
}

P3743 小鸟的设备

解题思路

  • 无限使用的条件:

    • 如果充电宝的充电速度 p 大于所有设备的总耗电速度之和,即 p >= sum(a_i),那么设备可以无限使用,直接输出 -1
  • 有限时间的情况:

    • 在设备无法无限使用的情况下,我们需要找出最长的运行时间 T。假设我们尝试使用二分法来计算这个时间 T
    • 对于一个时间 T,我们检查是否有一个充电策略能够保证所有设备在 T 秒后仍然有电。这可以通过计算设备在 T 秒内消耗的能量与充电宝能够提供的能量的关系来确定。
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        // 输入
        int n = input.nextInt();
        int p = input.nextInt();

        int[] a = new int[n];
        int[] b = new int[n];

        double totalA = 0;

        for (int i = 0; i < n; i++) {
            a[i] = input.nextInt();
            b[i] = input.nextInt();
            totalA += a[i];
        }

        // 如果充电宝的充电速度超过了所有设备的耗电总和,可以无限使用
        if (p >= totalA) {
            System.out.println("-1.000000");
            return;
        }

        // 二分查找时间T
        double left = 0, right = 1e10;

        while (right - left > 1e-6) { // 精度控制
            double mid = (left + right) / 2;

            double requiredEnergy = 0;

            for (int i = 0; i < n; i++) {
                double energyNeeded = a[i] * mid - b[i];
                if (energyNeeded > 0) {
                    requiredEnergy += energyNeeded;
                }
            }

            if (requiredEnergy <= p * mid) {
                left = mid;
            } else {
                right = mid;
            }
        }

        // 输出结果,精确到小数点后10位
        System.out.printf("%.10f\n", left);
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HeShen.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值