二分练习——卡牌

卡牌

题目分析

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

第一阶段二段性分析

对于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]的最大值是4e5,也就是每种牌我最多有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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值