二分以及练习题目

二分模板

判断是否可以二分

(1)单调性

备选答案集是有序的

(2)二段性

在检查了mid是否符合要求之和,我可以舍弃左右某一边的答案

两个模板

  1. 关键词:满足条件的最小值,最大值最小,某个有序区间内某个数>=或者>key的第一个元素,或者当正确答案在左边时

    while(l < r) {
     //check函数求当前值为mid情况下是否合法,如果合法,会想更小一点的值是不是合法的,所以会舍弃掉右边,去左边寻找更小的值,即r向左移动,因为mid是合法的,不能不要它,所以r=mid
    		if(check(mid)) {//满足要求,要求最小,再向左找看有没有更小的也满足要求
    			r = mid;//因为mid也满足要求,所以要保留
     //如果不合法,只能扩大当前值,所以会舍弃掉左边,去右边寻找更大的值,即l向右移动,因为mid是不合法的,直接不要它,所以l=mid+1
    		}else{
    			l = mid + 1;//mid不满足要求,直接跳过就行
    		}
    	}
    

    假设出现如下情况,l=l,r=l+1,此时mid=l。如果mid = (l+r)/2

    (1)check(mid)返回为真,r = mid = l,正常退出

    (2)check(mid)返回为假,l = r,正常退出

  2. 关键词:满足条件的最大值,最小值最大,某个有序区间内某个数<=或者<key的最后一个元素,或者当正确答案在左边时

while(l < r) {
		long mid = (l+r+1)/2;//注意这里要+1
		if(check(mid)) {//满足要求,要求最大,再向右找看有没有更大的也满足要求
			l = mid;//因为mid也满足要求,所以要保留
		}else{
			r = mid - 1;//mid不满足要求,直接跳过就行
		}
	}

假设出现如下情况,l=l,r=l+1,此时mid=l。如果mid = (l+r)/2

(1)check(mid)返回为真,l = mid = l,此时会陷入无限循环

(2)check(mid)返回为假,r < l,正常退出

假设出现如下情况,l=l,r=l+1,此时mid=l+1。如果mid = (l+r+1)/2

(1)check(mid)返回为真,l = mid = l+1=r,正常退出

(2)check(mid)返回为假,r = l,正常退出

分巧克力

题目分析

第一阶段二段性分析

希望巧克力的边长最大,而巧克力的边长要满足一个条件,就是按照该边长切出来的巧克力的块数应该大于等于K块才能满足K位小朋友都有巧克力。那么现在就是满足条件的最大值,我们要看一下他是否符合二段性,二分的关键在于二段性。

对于边长为mid的巧克力,如果它可以切出k块巧克力,那么我们可以确定边长小于mid的巧克力一定也可以,但是此时我需要找的是最大的边长,那么mid一定比小于mid的值更大,所以小于mid的值我就不用管了,也就是我可以确定我能够舍弃掉mid左边的值。我还想要确定比mid更大的边长是否也满足条件,所以我要在mid的右边继续二分。

if (check(mid)) {l = mid;} //因为mid是符合条件的,所以我要留着它,而不是l=mid+1

对于边长为mid的巧克力,如果它不可以切出k块巧克力,那么我们可以确定边长大于mid的巧克力一定也不可以,所以大于等于mid的值我就不用管了,也就是我可以确定我能够舍弃掉mid右边的值。我还想要寻找比mid更小的边长是否能满足条件,所以我要在mid的左边继续二分。

else { r = mid - 1; }//因为mid是不符合条件的,所以我不要留着它,而不是r=mid
//主要这里出现了减法,那么求mid那么应该是(l+r+1)/2

综上该题满足二段性,可以用二分,二分的板子就不说了,接下来说一下check函数如何写。

第二阶段写check函数

check(u)要实现的作用是检查边长为u的情况下能否切出k块巧克力。已知某块巧克力为 W ∗ H W*H WH大小,那么能够切出来的巧克力个数用 ( W / u ) ∗ ( H / u ) (W/u)*(H/u) (W/u)(H/u)表示。注意不能用 ( W ∗ H ) / ( u ∗ u ) (W*H)/(u*u) (WH)/(uu)表示。那么只需要遍历当前每一块巧克力,求出能个切割的巧克力总数和k比较就可以了,代码如下,

private static boolean check(int[][] a, int mid, int k) {
    int sum = 0;
    for (int i = 0; i < a.length; i++) {
        sum =sum + (a[i][0]/mid)*(a[i][1]/mid);
    }
    if(sum >= k) {
        return true;
    }
    return false;
}

第三步二分范围确定

那么这里的边长的最小值是1,最大值就是巧克力的最大边长,也就是100000。

题目代码

import java.util.Scanner;
public class Main {
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int n = scanner.nextInt();
    int k = scanner.nextInt();
    int a[][] = new int[n][2];
    for (int i = 0; i < n; i++) {
        a[i][0] = scanner.nextInt();
        a[i][1] = scanner.nextInt();
    }
    int l = 1;
    int r = 100000;
    while(l<r) {
        int mid = (l + r + 1) / 2;
        if(check(a,mid,k)) {
            l = mid;
        }else {
            r = mid - 1;
        }
    }
    System.out.println(l);
}

private static boolean check(int[][] a, int mid, int k) {
    int sum = 0;
    for (int i = 0; i < a.length; i++) {
        sum =sum + (a[i][0]/mid)*(a[i][1]/mid);
    }
    if(sum >= k) {
        return true;
    }
    return false;
}
}
#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int n,k,u;
int h[N],w[N];
//check函数 检查是否能切k块巧克力
int m;//巧克力块数
bool check(int mid){
    m=0;
	for(int i=0;i<n;i++){
		m+=(w[i]/mid)*(h[i]/mid);
	}
	if(m>=k) return true;
    else return false;
}
int main(){
	cin>>n>>k;
	int l,r;
	 l=1;r=1e5;
	for(int i=0;i<n;i++){
		cin>>h[i]>>w[i];
	}
	int mid;
	while(l<r){
		mid=(l+r+1)/2;
		if(check(mid)) l=mid;
		else r=mid-1;
		}
	cout<<l;
	return 0;
}
n,k=map(int,input().split())
a=[]
for i in range(n):
  x,y=map(int,input().split())
  a.append((x,y))
def check(m):
  n=0
  for x,y in a:
     n +=(x//m)*(y//m)
  return n>=k
left,right=1,100000
ans=1
while left<right:
  mid=(left+right+1)//2
  if check(mid):
    left=mid
  else:
    right=mid-1
print(left)

求阶乘

题目分析

第一阶段二分板子分析

我们要求满足N!末尾0的个数恰好为K的最小的N是不是就是求满足条件的最小,还是要考虑一下二分。

如果对于mid!末尾0的个数比K少,那么所有比mid小的值的阶乘包含的5的个数肯定更少,所以我可以把mid左边的值都舍弃掉,继续向mid右边寻找。

if(f(mid) < k ) {  l = mid + 1; }//此时mid是不符合条件的,我要舍弃它,所以l=mid+1,而不是mid

如果对于mid!末尾0的个数比K大,那么所有比mid大的值的阶乘包含的5的个数肯定更多,所以我可以把mid右边的值都舍弃掉,继续向mid左边寻找。

else { r = mid; }//此时mid是符合条件的,我要留着它,所以r=mid,而不是mid-1

综上满足二分的二段性,而check函数就是求解N!里面5的个数,接下来讲解。

第二阶段写check函数

如果直接去求,我们看一下,K的范围已经到了 1 18 1^{18} 118,这么大肯定有什么巧妙的地方,而不是直接求解。对于一个数字N而言,我们怎么去求他的阶乘末尾包含0的个数?末尾的0是如何产生的?是不是由 2 ∗ 5 = 10 2*5=10 25=10产生的?我们只需要求N!里面包含几个2或者几个5就行了,那么由2或者5个数的最小值决定末尾0的个数,那么2和5而言,比如1-20里面,5,10,15,20包含5,而2,6,8,10,12,14,16,20里面包含2,也就是5的个数必然比2少,那么只需要求N!里面包含5的个数就可以了。那么这个求法大家可以记住,代码如下,

private static long f(long x) {
    long res = 0;
    while(x > 0) {
        res += x / 5;
        x = x / 5;
    }
    return res;
}

求x!里面包含5的个数,令x/5,直到x的值为0就停止。比如25里面5的个数等于sum+=25/5=sum+=5。25/5=5不等于0,所以还要再来一次sum+=5/5=sum+=1。此时就可以了。那么25里面包含6个5。其中5,10,15,20,都包含1个5,而25里面两个5,所以一共6个5。

第三阶段二分范围确定

这一道题,l=0,但是r应该如何确定呢?第一种方法直接一点,k=1e18,明显是long的范围,那么r我就初始化为long的最大值Long.MAX_VALUE是完全没有问题的。第二种方法,你要去求了,求一个精确的值,那么怎么求呢?我要求谁的阶乘末尾0的个数是1e18,你可以一个个试,

我试的过程如下图,最终得出了5e18就可以。

check函数是第二步介绍的,来求某个数的阶乘后面0的个数。

但是注意这里,我不能确定一定可以找到一个满足条件的解,对于分巧克力那道题,题目说了每个小朋友能够至少获得一块边长为1的巧克力,所以边长为1一定满足条件,一定有解。而本题也说了,这样的N可能不存在,所以在二分结束后,我要确定一下,当前的值是否满足条件。

题目代码

import java.util.Scanner;
public class Main {
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    long k = scanner.nextLong();
    long l = 1,r = Long.MAX_VALUE-10;
    while(l < r) {
        long mid = (l+r)/2;
        if(f(mid) < k ) {
            l = mid + 1;
        }else {
            r = mid;
        }
    }
    if(f(l) == k) {
        System.out.println(l);
    }else {
        System.out.println(-1);
    }
   
}

private static long f(long x) {
    long res = 0;
    while(x > 0) {
        res += x / 5;
        x = x / 5;
    }
    return res;
}
}

卡牌

题目分析

想一下前面题的特点,是不是都出现了“最大边长”,“最小的数”这种字眼,那么这里出现了“最多能凑出多少套牌”,我们可以考虑用二分。接下来我们要看一下他是否符合二段性,二分的关键在于二段性。

第一阶段二段性分析

对于mid套牌,如果我们可以凑出来,那么我们可以确定套数小于mid的牌一定也可以,但是此时我需要找的是最多,那么mid一定比小于mid的值更大,所以小于mid的值我就不用管了,也就是我可以确定我能够舍弃掉mid左边的值。我还想要确定比mid更大的值是否也满足条件,所以我要在mid的右边继续二分。

if (check(mid)) {l = mid;} //因为mid是符合条件的,所以我要留着它,而不是l=mid+1

对于mid套牌,如果我们不可以凑出来,那么我们可以确定大于mid的套数一定也不可以,所以大于等于mid的值我就不用管了,也就是我可以确定我能够舍弃掉mid右边的值。我还想要寻找比mid更小的值是否能满足条件,所以我要在mid的左边继续二分。

else { r = mid - 1; }//因为mid是不符合条件的,所以我不要留着它,而不是r=mid
//主要这里出现了减法,那么求mid那么应该是(l+r+1)/2

综上该题满足二段性,可以用二分,二分的板子就不说了,接下来说一下check函数如何写。

第二阶段写check函数

check(u)要实现的作用是检查我能否凑出u套牌,也就是n种牌每一种的张数至少是n。我现在有m张空牌可以用,但是也有一个限制对于第i种牌,我手写的个数不能超过b[i]。整体思路是如果a[i]<u,我要手写u-a[i]张牌,如果u-a[i]>b[i],然后我还要记录目前我手写的总牌数,超过m也是返回false。我可以返回false具体请看代码,

public static boolean check(int num) {//num为可以凑出来的卡牌组数
        long temp = m;//备份空白牌的数量
        for (int i = 0; i < a.length; i++) {//遍历卡牌
            if (a[i] >= num) continue;
            //如果卡牌数现在不满足至少为num张
            int add = num - a[i];//需要添加的扑克牌数量
            temp -= add;
            if (temp < 0) return false;//如果剩余的牌不够
            if (add > b[i]) return false;//如果超过预计需要画的牌个数
        }
        return true;
    }

第三阶段二分范围确定

l的值好确定,就是0,那么r呢?先来看一下a[i]的最大值是1e4,也就是每种牌我最多有4e5张,b[i]也是最多可以手写4e5张,那么加起来就是8e5,因此r可以取8e5+5。后面的5是随便加的数,一般要比你算出来的大一些就可以。

题目代码

import java.util.Scanner;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
public class Main {
    static int n;
    static long m;
    static int[] a, b;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String line = br.readLine();
        n = Integer.parseInt(line.split(" ")[0]);
        m = Long.parseLong(line.split(" ")[1]);//空白牌的数量
        a = new int[n];
        b = new int[n];
        int l = 0, r = 800005;
        String[] s = br.readLine().split(" ");
        for (int i = 0; i < n; i++) {
            a[i] = Integer.parseInt(s[i]);
        }
        s = br.readLine().split(" ");
        for (int i = 0; i < n; i++) {
            b[i] = Integer.parseInt(s[i]);
        }
        while (l < r) {//获取最大值
            int mid = (l + r + 1) / 2;
            if (check(mid)) {
                l = mid;
            } else {
                r = mid - 1;
            }
        }
        System.out.println(l);
    }
    public static boolean check(int num) {//num为可以凑出来的卡牌组数
        long temp = m;//备份空白牌的数量
        for (int i = 0; i < a.length; i++) {//遍历卡牌
            if (a[i] >= num) continue;
            //如果卡牌数现在不满足
            int add = num - a[i];//需要添加的扑克牌数量
            temp -= add;
            if (temp < 0) return false;//如果剩余的牌不够
            if (add > b[i]) return false;//如果超过预计需要画的牌个数
        }
        return true;
    }
}
#include <stdio.h>
#include <stdbool.h>
#define N 1000010

int n;
long long m;
int a[N], b[N];

bool check(int x){
	long long s = 0;
	for (int i = 0; i < n; i ++){
		if (a[i] >= x)
			continue;
		if (x - a[i] <= b[i])
			s += x - a[i];
		else return false;
	}
	if (s <= m)
		return true;
	return false;
}
int main(){
	scanf("%d%lld", &n, &m);
	for (int i = 0; i < n; i ++){
		scanf("%d", &a[i]);
	}

	for (int i = 0; i < n; i ++){
		scanf("%d", &b[i]);
	}
	int l = 0;
	int r = 8e8;
	while (l < r){
		int mid = (l + r + 1) / 2;
		if (check(mid)){
			l = mid;
		}
		else
			r = mid - 1;
	}
	printf("%d",l);
	return 0;
}

美丽的区间

题目分析

读题发现了一个关键字,“区间长度最短”,考虑用二分。对于一个长度为mid的区间,如果它的左右端点都不固定,其实我们不知道这个区间是哪个,因为长度为2的区间,可以是区间[1,2]也可以是区间[2,3]。所以我们固定区间端点,那么此时我们枚举左边界i,左边界二分去找,那么此时的mid表示的是区间右边界。如果mid往右走变大,区间长度会增大,如果mid往左走变小,区间长度会变小。区间长度为mid-i+1。

第一阶段二段性分析

对于长度为mid-i+1的区间,如果我们可以找到它的区间和大于等于S,那么我们可以确定右边界大于mid的区间一定也可以,因为区间里的值都是正数。但是此时我需要找的是最短,那么mid一定比大于mid的值更小,所以大于mid的值我就不用管了,也就是我可以确定我能够舍弃掉mid右边的值。我还想要确定比mid更小的值是否也满足条件,所以我要在mid的左边继续二分。

if(sum[mid] - sum[i-1] >= s) { r = mid;}//因为mid是符合条件的,所以我要留着它,而不是r=mid-1

对于长度为mid-i+1的区间,如果我们不可以找到它的区间和大于等于S,那么我们可以确定右边界小于mid的区间一定也不可以,因为区间里的值都是正数。所以小于mid的值我就不用管了,也就是我可以确定我能够舍弃掉mid左边的值。我还想要找比mid更大的值是否可以满足条件,所以我要在mid的右边继续二分。

else {l = mid + 1;}//因为mid是不符合条件的,所以我不要留着它,而不是l=mid

综上该题满足二段性,可以用二分,二分的板子就不说了,接下来说一下check函数如何写。

第二阶段写check函数

check(u)要实现的作用是检查区间和是否大于等于S,而求区间和最快的方式是利用前缀和求。

for (int i = 1; i < a.length; i++) {
        a[i] = scanner.nextInt();
        sum[i] = sum[i-1] + a[i];
    }
.......
if(sum[mid] - sum[i-1] >= s) {
       r = mid;
}else {
       l = mid + 1;
}

第三阶段二分范围确定

mid表示的是右边界,当左边界为i时,右边节最小去i,最大取n。

每次二分结束,我都要确定一下当前得到的l是否满足条件。最后输出的时候还是要确定一下我是否找到了满足条件的区间,因为这样的区间可能不存在。
mid表示的是右边界,当左边界为i时,右边节最小去i,最大取n。

每次二分结束,我都要确定一下当前得到的l是否满足条件。最后输出的时候还是要确定一下我是否找到了满足条件的区间,因为这样的区间可能不存在。

解释一下下面的代码,

if(sum[r] - sum[i-1] >= s) {
            ans = Math.min(ans, r-i+1);    
    }

为什么会有它呢?我们要知道二分求出来的l是什么,我们求出来的l是在区间左端点固定为i的情况下的一个答案,我们有多个左端点,也意味着我们有多个答案,那么我们求最短区间,就在这多个答案里求一个最小值就可以了,那么如何来表示答案呢。还是要注意我们二分求出来的l是左端点为i的情况下的右端点,那么我们的答案是区间长度,怎么表示呢?就是r-i+1,就是区间[i,l]的长度,因为这里是二分,l和r其实是相等的,写成r刚好和右端点对应,所以这里是r-i+1。

题目代码

import java.util.Scanner;
public class Main {
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int n = scanner.nextInt();
    long s = scanner.nextLong();
    int a[] = new int[n+1];
    long sum[] = new long[n+1];
    for (int i = 1; i < a.length; i++) {
        a[i] = scanner.nextInt();
        sum[i] = sum[i-1] + a[i];
    }
    long ans = Integer.MAX_VALUE;
    for (int i = 1; i < n + 1; i++) {//枚举右边界
        int l = i,r = n;
        while(l < r) {
            int mid = (l + r)/2;
            if(sum[mid] - sum[i-1] >= s) {
                r = mid;
            }else {
                l = mid + 1;
            }
        }
        if(sum[r] - sum[i-1] >= s) {
            ans = Math.min(ans, r-i+1);    
        }
    }
    if(ans == Integer.MAX_VALUE) {
        System.out.println(0);
    }else {
        System.out.println(ans);
    }
}
}

123

二分+等差数列求和+前缀和数组

题目分析

连续一段的和我们想到了前缀和,但是这里的l和r的范围为1e12,明显不能用O(n)的时间复杂度去求前缀和。那么我们开始观察序列的特点,可以按照等差数列对序列进行分块。如上图,在求前10个数的和的时候,我们可以这样求1+3(1+2)+6(1+2+3)+10(1+2+3+4)=20。我们不需要遍历10次就可以求出来。求前缀和代码如下

sum = new long[1500010];
for (int i = 1; i <= 1500000; i++) {
    t += i;
    sum[i] = sum[i-1]+t;
}

这里的t最开始等于1,是第一块的数字和,然后等于3,是第二块数字的和,然后等于6是第三块数字的和,以此类推。sum[i]表示的是分块后前i块包含数字的和。

我们可以求出前n块数字的和,那么我们需要知道第l个数字是在哪一块里面,然后求第l个数字是所在块的第几个数字。因为对于最后一块来说,不是所有的数字都会被包含进来,我们要单独对最后一块求数字和。

第一阶段利用二分求第x个数所在的块

​ 图1

如图1所示,我们可以把这个序列分块,第一块有1个数,第二块有2个数,第三块有3个数,第四块有4个数,每一块拥有数的个数是一个等差数列。现在要找到序列中第x个数所在的块数,假设x=3,那么第x个数在第2块中,如果x=4,那么第x个数在第3块中。求第x个数所在的块数,就是求从左往右数,前缀和第一个大于等于x的块。

比如第2块的前缀和就是3,第三块的前缀和就是5。这个可以用二分去求。

    int l = 1;
    int r = 1500000;//最多可以分的块数
    while(l < r) {
        int mid = (l + r) / 2;
        if(sum(mid) < x) {//求mid个块中包含的数字的个数,如果小于x,就是不符合条件,我要向左找
            l = mid + 1;
        }else {//符合条件,我要看前面的块是否也是大于等于x的
            r = mid;
        }
    }

这里的sum函数的作用就是求前mid块中包含的数字的个数,因为是等差数列,所以直接用等差数列的求和公式就可以了,sum函数如下

private static long sum(long x) {    
    return (1L + x) * x / 2;
}

第二阶段求前x个数的前缀和

接下来分析二分结束之和我要怎么操作,我要求前x个数字的和。

假设x=8,那么第x个数在第4块中,我还要知道,第x个数是第4块中的第几个数字。如图,第4块有4个数,第x个数第4块的第2个数上,那么第2个数是怎么来的呢?就是x-sum(r-1)=8-6=2。其实就是我二分算出来了第x个数在第r块上,那么我只需要把前r-1块包含的数的个数减去就算出来x是在第r块上的第几个数上了。结合上图更好理解。那么前x个数的和就是前r-1块包含数的和加上第r块里面前x-sum(r-1)个数的和。

某一块里面包含的数也是等差数列,求前n个数的和依然可以直接用sum(n)去求。而数组sum[r]里面记录的是前r块包含数字值的前缀和。所以二分结束后的代码是这样子的

private static long f(long x) {
    int l = 1;
    int r = 1500000;//最多可以分的块数
    while(l < r) {
        int mid = (l + r) / 2;
        if(sum(mid) < x) {//求mid个块中包含的数字的个数
            l = mid + 1;
        }else {
            r = mid;
        }
    }
    //r--是表示完整的块的个数
    r--;//就是上文里的r-1
    //x表示x处在他所在块的第几个位置,需要减去前面所有块包含的数的个数
    //本来要求x个数字,前r个块中已经包含了sum(r)个数字,第r+1个块
    x -= sum(r);//前r个块中已经包含了多少个数字
    return sum[r]+sum(x);
}

还是对于x=8的例子,二分求出来r=4,r–后,r=3,sum[3]=10。x-=sum(3)=8-6=2。sum[3]+sum(2)=10+3=13

注意这道题里对于sum函数的多次应用,但是不是每一次应用含义都是一样的。因为每一块包含的数字个数是等差数列,而每一块内每个数字形成的也是等差数列。

题目代码

import java.util.Scanner;
public class Main {
static long[] sum;
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    long t = 0;
    //前缀和的预处理
    sum = new long[1500010];
    for (int i = 1; i <= 1500000; i++) {
        t += i;
        sum[i] = sum[i-1]+t;
    }
    int n = scanner.nextInt();
    while(n-- > 0) {
        long l = scanner.nextLong();
        long r = scanner.nextLong();
        System.out.println(f(r)-f(l-1));//f(r)求的是序列中前r个数的和
    }
}
//二分  二分求的是x在哪一块中 n*(n-1)/2>l的第一个n
private static long f(long x) {
    int l = 1;
    int r = 1500000;//最多可以分的块数
    while(l < r) {
        int mid = (l + r) / 2;
        if(sum(mid) < x) {//求mid个块中包含的数字的个数
            l = mid + 1;
        }else {
            r = mid;
        }
    }
    //r--是表示完整的块的个数
    r--;
    //x表示x处在他所在块的第几个位置,需要减去前面所有块包含的数的个数
    //本来要求x个数字,前r个块中已经包含了sum(r)个数字,第r+1个块
    x -= sum(r);//前r个块中已经包含了多少个数字
    return sum[r]+sum(x);
}
//sum函数求前x块包含的数的个数,同时也可以表示某一个块中,前x个数的总和
private static long sum(long x) {
    // TODO Auto-generated method stub    
    return (1L + x) * x / 2;
}
}

晾衣服

二分

题目分析

这里出现了“最小化干燥的总时间”,那么可以考虑用二分去做。

第一阶段二段性分析

假设当前需要耗费的时间为mid分钟,如果mid分钟内可以烘干这些衣服,那么我们可以确定右边界大于mid的区间一定也可以。但是此时我需要找的是最短时间,那么mid一定比大于mid的值更小,所以大于mid的值我就不用管了,也就是我可以确定我能够舍弃掉mid右边的值。我还想要确定比mid更小的值是否也满足条件,所以我要在mid的左边继续二分。

if(check(mid)) {r = mid;}//因为mid是符合条件的,所以我要留着它,而不是r=mid-1

假设当前需要耗费的时间为mid分钟,如果mid分钟内不可以烘干这些衣服,那么我们可以确定右边界小于mid的区间一定也不可以。所以小于mid的值我就不用管了,也就是我可以确定我能够舍弃掉mid左边的值。我还想要找比mid更大的值是否可以满足条件,所以我要在mid的右边继续二分。

else {l = mid + 1;}//因为mid是不符合条件的,所以我不要留着它,而不是l=mid

综上该题满足二段性,可以用二分,二分的板子就不说了,接下来说一下check函数如何写。

第二阶段写check函数

check(u)要实现的作用是检查能否在mid分钟内烘干这些衣服。对于一个衣服的湿度a[i],如果他大于mid,就需要使用烘干机,使用的时间是(a[i]-mid)/(k-1)。因为烘干衣服不足1分钟也要按一分钟算,所以这里要上取整。

这里为什么是k-1呢?我们要保证在mid分钟内烘干,因为衣服每一分钟湿度都会减1,如果我把衣服放在烘干机上湿度会减k,那么放在烘干机和不放在烘干机上会比原来多减了k-1。我们要注意的是,烘干机使用的总时间不能超过mid。

假设我只有一个衣服需要烘干,如果mid=3,a[i]=10,k=3。a[i]-mid=10-3=7。如果是7/3=3。这样看mid等于3是可以的,但是我把烘干机全用在这一个衣服上所需要的烘干时间是10/3=4>mid=3,明确不可以在3分钟内烘干该衣服。所以我这里应该是7/2=4,此时可以判断出来不可以在3分钟内烘干该衣服。check函数如下,

static boolean check(int mid) {
        long s = 0;
        for (int i = 0; i < n; i++) {
            if (a[i] > mid) {
                s += (long)(a[i] - mid + k - 2) / (k - 1);
            }
        }
        return s <= mid;
    }

第三阶段二分范围确定

烘干的时间最长就是不使用烘干机,自然风干需要a[i]分钟,而a[i]最大是1e9,所以l=0,r=1e9。

注意一个特殊情况,如果k=1,那么其实烘干机有和没有都一样,自然风干所需要的时间就是所有衣服中最大的湿度。

for (int i = 0; i < n; i++) {
        a[i] = sc.nextInt();
        maxV = Math.max(maxV, a[i]);
}
k = sc.nextInt();
if (k == 1) {
    System.out.println(maxV);
    return;
}

题目代码

import java.util.Scanner;
public class Main {
    static int N = 100010;
    static int n, k;
    static int[] a = new int[N];
    public static void main(String[] args) {
    	Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        int maxV = 0;
        for (int i = 0; i < n; i++) {
            a[i] = sc.nextInt();
            maxV = Math.max(maxV, a[i]);
        }
        k = sc.nextInt();
        if (k == 1) {
            System.out.println(maxV);
            return;
        }
        int l = 1, r = (int) 1e9;
        int res = 0;
        while (l < r) {
            int mid = (l + r)/2;
            if (check(mid)) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }
        System.out.println(l);
    }

    static boolean check(int mid) {
        long s = 0;
        for (int i = 0; i < n; i++) {
            if (a[i] > mid) {
                s += Math.ceil((a[i]-mid)*1.0 / (k - 1));
            }
        }
        return s <= mid;
    }
}

妮妮的月饼工厂

题目分析

这里出现了“最高高度”,那么可以考虑用二分去做。

第一阶段二段性分析

希望月饼的高度最大,而月饼的高度要满足一个条件,就是按照该高度切出来的月饼的块数应该大于等于K块。那么现在就是满足条件的最大值,我们要看一下他是否符合二段性,二分的关键在于二段性。

对于高度为mid的月饼,如果它可以切出k块月饼,那么我们可以确定高度小于mid的月饼一定也可以,但是此时我需要找的是最大的高度,那么mid一定比小于mid的值更大,所以小于mid的值我就不用管了,也就是我可以确定我能够舍弃掉mid左边的值。我还想要确定比mid更大的高度是否也满足条件,所以我要在mid的右边继续二分。

if (check(mid)) {l = mid;} //因为mid是符合条件的,所以我要留着它,而不是l=mid+1

对于高度为mid的月饼,如果它不可以切出k块月饼,那么我们可以确定边长大于mid的月饼一定也不可以,所以大于等于mid的值我就不用管了,也就是我可以确定我能够舍弃掉mid右边的值。我还想要寻找比mid更小的月饼是否能满足条件,所以我要在mid的左边继续二分。

else { r = mid - 1; }//因为mid是不符合条件的,所以我不要留着它,而不是r=mid
//主要这里出现了减法,那么求mid那么应该是(l+r+1)/2

综上该题满足二段性,可以用二分,二分的板子就不说了,接下来说一下check函数如何写。

第二阶段写check函数

check(u)要实现的作用是检查高度为u的情况下能否切出k块月饼。已知某个月饼高度为 H H H大小,那么能够切出来的月饼个数用 ( H / u ) (H/u) (H/u)表示。那么只需要遍历当前每一块月饼,求出能个切割的月饼总数和k比较就可以了,代码如下,

private static boolean check(int u) {
	long res = 0l;
	for(int i = 1;i <=n;i++) {
		res += a[i]/u;
	}
	if(res >= k) return true;
	return false;
}

第三步二分范围确定

那么这里的高度的最小值是1,最大值就是月饼的最大边长,也就是1e9。注意本题没有说一定能找到符合条件的高度,所以最后输出的时候要判断一下。

题目代码

import java.util.Scanner;
public class Main {
	static long a[];
	static int n;
	static long k;
public static void main(String[] args) {
	Scanner scanner = new Scanner(System.in);
	n = scanner.nextInt();
	k = scanner.nextLong();
	a = new long[n+1];
	for(int i = 1;i<=n;i++) {
		a[i]=scanner.nextInt();
	}
	int l = 1,r = (int) 1e9;
	while(l < r) {
		int mid = (l+r+1)/2;
		if(check(mid)) l = mid;
		else r = mid-1;
	}
	if(!check(l)) System.out.println(-1);//忘了这个判断
	else System.out.println(l);
}
private static boolean check(int u) {
	// TODO Auto-generated method stub
	long res = 0l;
	for(int i = 1;i <=n;i++) {
		res += a[i]/u;
	}
	if(res >= k) return true;
	return false;
}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/434853.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Django学习记录08——图表及文件上传案例

1.图表Echarts的应用 Apache ECharts 1.1 使用方法 引用echarts.js即可到官方文档中查询使用 1.2 常用图标的使用 图表展示页面的部署&#xff08;主要展示折线图、柱状图、饼图&#xff09; {% block content %}<div class"container"><div class&qu…

李沐动手学习深度学习——4.1练习

1. 计算pReLU激活函数的导数。 pReLU激活函数公式根据课本有如下&#xff1a; p R e L U ( x ) max ⁡ ( 0 , x ) α min ⁡ ( 0 , x ) \mathrm{pReLU}(x) \max(0, x)\alpha \min(0,x) pReLU(x)max(0,x)αmin(0,x) 对应的函数图像为: 对应的导数计算为&#xff1a; d p R…

github双因子认证

最近换了个安卓手机&#xff0c;打算让之前的苹果手机退役了&#xff0c;所以需要重新搞GitHub的Two-factor authentication 步骤如下&#xff1a; 1. 访问安全中心 https://github.com/settings/security 2. 点击Authenticator app右侧按钮 3. 下载腾讯身份验证器&#xff…

解密程序员的“藏宝图”:我的祖传代码大公开

程序员是如何看待“祖传代码”的&#xff1f; 大家好&#xff0c;我是小明&#xff0c;一位充满好奇心和分享热情的程序员。今天&#xff0c;我要为大家揭开我心中的“藏宝图”——那些我认为值得传世的祖传代码。让我们一同踏上这场奇妙的代码冒险之旅吧&#xff01; 宝物一…

WMS仓储管理系统在电子企业中的应用效果如何

一、WMS仓储管理系统的概念与重要性 在当今信息化与自动化的时代&#xff0c;仓储管理不再仅仅是简单的物品存储和分发。仓库作为供应链中的核心环节&#xff0c;其管理效率和准确性直接影响到企业的运营成本和客户满意度。WMS仓储管理系统应运而生&#xff0c;成为电子企业提…

代码随想录算法训练营第三十八天|509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯

509. 斐波那契数 刷题https://leetcode.cn/problems/fibonacci-number/description/文章讲解https://programmercarl.com/0509.%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE视频讲解https://www.bilibili.com/video/BV…

微服务如何保证对外接口的安全?可以这样做!

如果你的微服务需要向第三方开放接口&#xff0c;如何确保你提供的接口是安全的呢&#xff1f; 1. 什么是安全接口 通常来说&#xff0c;要将暴露在外网的 API 接口视为安全接口&#xff0c;需要实现防篡改和防重放的功能。 1.1 什么是篡改问题&#xff1f; 1.1.1 如何解决篡…

GEE 依照范围裁剪 下载Sentinel-2数据

0. GEE介绍 Google Earth Engine&#xff08;GEE&#xff09; 是由Google开发的一种云端平台&#xff0c;旨在提供强大的地理空间数据处理和分析工具。GEE集成了大量的遥感影像数据和地理空间数据集&#xff0c;以及高性能的计算资源&#xff0c;使用户能够在云端高效地进行大规…

【深度学习笔记】 5_9 含并行连结的网络(GoogLeNet)

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 5.9 含并行连结的网络&#xff08;GoogLeNet&#xff09; 在2014年的ImageNet图像识别挑战赛中&#xff0c;一个名叫GoogLeNet的网络结…

Pycharm与Anaconda安装

网址&#xff1a; Pycharm&#xff1a;https://www.jetbrains.com/pycharm/ Anaconda&#xff1a;https://www.anaconda.com/download/ 官网下载速度太慢可以选择到清华源下载&#xff1a;https://repo.anaconda.com/archive/ 一&#xff1a;Anaconda安装 安装&#xff1a; …

Android开发社招面试经验,大佬带你看源码

程序员的劫 最近&#xff0c;又被程序员年龄的事情刷屏了。37岁被公司优化&#xff0c;找工作几个月都没有很好的归属&#xff0c;所谓的小公司还看不上。等等类似的话题变成了程序员的吐槽固定标题&#xff0c;无论是程序员&#xff0c;还是其他行业人员&#xff0c;都可以就…

把简单留给用户,把复杂交给 AI

2024 年伊始&#xff0c;Kyligence 联合创始人兼 CEO 韩卿&#xff08;Luke&#xff09;分享了对 AI 与数据行业的一些战略思考&#xff0c;以及对中美企业服务市场的见解&#xff0c;引发业界同仁的广泛共鸣。正值 Kyligence 成立 8 周年&#xff0c;恰逢 AI 技术应用风起云涌…

开发一个小程序需要多少钱

【腾讯云】 爆款2核2G3M云服务器首年 61元&#xff0c;叠加红包再享折上折 要做小程序的小伙伴们福利来啦&#xff01; 抢购方式&#xff1a; 方式一&#xff1a;直达链接》点我直接抢购 方式二&#xff1a;长按住识别以下官方二维码直接抢购 01 小程序认证 根据微信公众平台…

1.2_1 分层结构、协议、接口和服务

1.2_1 分层结构、协议、接口和服务 &#xff08;一&#xff09;为什么要分层&#xff1f; 主机A如果想要向主机B发送文件&#xff0c;则一定要经过中间的一些介质、链路。 发送文件前要完成的工作&#xff1a; 1.发起通信的计算机必须将数据通信的通路进行激活。 所谓的激活&a…

经典算法----折半查找

二、经典算法之折半查找 很多同学对于二分法就是&#xff1a;一看就会&#xff0c;一写就废&#xff01;&#xff01;&#xff01;&#xff01; 易错点1&#xff1a;以下循环方式写哪一个&#xff1f; 方案一&#xff1a;while(left<right) 方案二&#xff1a;while(left…

[蓝桥杯 2017 省 A] 油漆面积 Java代码及一些个人理解

[蓝桥杯 2017 省 A] 油漆面积 题目描述 X 星球的一批考古机器人正在一片废墟上考古。 该区域的地面坚硬如石、平整如镜。 管理人员为方便&#xff0c;建立了标准的直角坐标系。 每个机器人都各有特长、身怀绝技。它们感兴趣的内容也不相同。 经过各种测量&#xff0c;每个…

uniapp小程序获取位置权限(不允许拒绝)

需求 小程序上如果需要一些定位功能&#xff0c;那么我们需要提前获取定位权限。我们页面的所有功能后续都需要在用户同意的前提下进行&#xff0c;所以一旦用户点了拒绝&#xff0c;我们应该给予提示&#xff0c;并让用于修改为允许。 实现 1.打开手机GPS 经过测试发现即使…

Java精品项目--第6期基于SpringBoot的茶叶商城的设计分析与实现

项目技术栈 SpringBootMavenMySQLJAVAMybatis-PLusVue.js&#xff08;非前后端分离&#xff09;Element-UI&#xff08;非前后端分离&#xff09;… 表截图 项目截图

Pygame教程05:帧动画原理+边界值检测,让小球来回上下运动

------------★Pygame系列教程★------------ Pygame教程01&#xff1a;初识pygame游戏模块 Pygame教程02&#xff1a;图片的加载缩放旋转显示操作 Pygame教程03&#xff1a;文本显示字体加载transform方法 Pygame教程04&#xff1a;draw方法绘制矩形、多边形、圆、椭圆、弧…

MES系统是怎么进行数据采集的?

在MES管理系统中&#xff0c;数据采集作为最基础也最为关键的一环&#xff0c;对于实现生产过程的透明化、可控好以及优化生产流程具有重要意义。 mes系统是怎么采集数据的? 一、PLC类数据采集&#xff1a;使用C#或C直接编程访问PLC(不需要花钱买组态软件或第三方软件) 二、…