二分答案
【参考:二分答案_哔哩哔哩_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;,继续找更小的
}
}