蓝桥杯倒计时48天!二分模板

倒计时48天!

二分

二分模板
判断是否可以二分

(1)单调性

备选答案集是有序的

(2)二段性

在检查了mid是否符合要求之和,我可以舍弃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. 某个有序区间内某个数<=或者

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

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

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

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

对于mid=(l+r+1)/2,假设出现如下情况,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,正常退出

对于这两个模板,不需要硬记,在做题的过程中边分析边写完全没问题,记住如果分析的过程中出现了“—”号,一定要在mid这里除以2的时候加1。可以告诉自己本身(l+r)/2就是下取整,有损失,这里又出现了减法,损失更大了,所以要写成(l+r+1)/2。

分巧克力
题目分析

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

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

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

综上该题满足二段性,可以用二分,二分的板子就不说了,接下来说一下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(mid == 1) break;
        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;
}
}
求阶乘
题目分析

如果直接去求,我们看一下,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。

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

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

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

综上满足二分的二段性,而check函数就是求解N!里面5的个数,前面已经讲解了。

但是注意这里,我不能确定一定可以找到一个满足条件的解,对于分巧克力那道题,题目说了每个小朋友能够至少获得一块边长为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;
}
}

你可能感兴趣的:(蓝桥杯)