蓝桥杯:卡牌https://www.lanqiao.cn/problems/2191/learning/
目录
问题描述
输入格式
输出格式
样例输入
样例输出
样例说明
数据范围
题目分析(贪心+二分)
AC代码(Java):
这天, 小明在整理他的卡牌。
他一共有 n 种卡牌, 第 i 种卡牌上印有正整数数 i(i∈[1,n]), 且第 i 种卡牌 现有 ai 张。
而如果有 n 张卡牌, 其中每种卡牌各一张, 那么这 n 张卡牌可以被称为一 套牌。小明为了凑出尽可能多套牌, 拿出了 m 张空白牌, 他可以在上面写上数 i, 将其当做第 i 种牌来凑出套牌。然而小明觉得手写的牌不太美观, 决定第 i 种牌最多手写 bi 张。
请问小明最多能凑出多少套牌?
输入共 3 行, 第一行为两个正整数 n,m 。
第二行为 n 个正整数 a1,a2,…,an 。
第三行为 n 个正整数 b1,b2,…,bn 。
一行, 一个整数表示答案。
4 5
1 2 3 4
5 5 5 5
3
这 5 张空白牌中, 拿 2 张写 1 , 拿 1 张写 2 , 这样每种牌的牌数就变为了 3,3,3,43,3,3,4, 可以凑出 3 套牌, 剩下 2 张空白牌不能再帮助小明凑出一套。
对于 30% 的数据, 保证 n≤2000;
对于 100% 的数据, 保证 n≤2×10^5; ai,bi≤2n; m≤n^2 。
这道题目如果按照暴力来思考的话,就是先找出给出的牌中能够拼凑出多少套,然后依次给最低的几个牌补1,直到m没有。
根据数据范围来看,如果是N^2,肯定会超时。所以选择比N^2时间复杂度小的算法来处理,那么就应该时间复杂度为N(log2N)的二分了。
二分的前提,是满足二段性,那么我们假设可以拼凑出X套牌。那么二段性就分为我们是否可以拼凑出X张牌。
之后写一个check算法,检查a[i]是否够X,如果不够,那么判断b[i](可以用空白牌添加)加上a[i]是否能够X,如果够了,那么我们就从空白牌m之中减去这部分,相当于将空白牌添加上去了。如果空白牌的数量不够,那么肯定是不能拼凑成X张牌的。然后继续判断下一张牌,最终遍历完成,能够将所有卡牌拼凑出X,那么肯定能够拼凑出X张牌。
因此我们二分N (牌的种类) 即可。
需要注意的是,m的范围是n^2,那么肯定会超出整型,必须用long来接受。
还有就是这道题是C++组的国赛题目,对于C++来说,运行时间1s处理这个数据是肯定没有问题的。但是Java如果用Scanner,就会超时,所以要换成StreamTokenizer。
Scanner (只过30%,其余超时):
StreamTokenizer(AC):
import java.util.*;
import java.io.*;
// 1:无需package
// 2: 类名必须Main, 不可修改
public class Main {
static int N;
static long M;
static int[] card;
static int[] upperLimit;
static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
public static int nextInt() throws IOException{
st.nextToken();
return (int) st.nval;
}
public static long nextLong() throws IOException{
st.nextToken();
return (long) st.nval;
}
public static void init() throws IOException{
N = nextInt(); //一共有n种牌
M = nextLong(); //空白牌
card = new int[N+1];
upperLimit = new int[N+1];
for(int i = 1;i<=N;i++) {
card[i] = nextInt();
}
for(int i = 1;i<=N;i++) {
upperLimit[i] = nextInt();
}
}
public static void main(String[] args) throws IOException{
init();
//二分枚举,不然操作太麻烦了
//我们设定一个x,二段性为x是否可以凑出x套牌。
int left = 1;
int right = N;
long ans = 0;
while(left < right) {
int middle = (left+right) >> 1;
if(check(middle)) { //可用凑出x套牌,
ans = Math.max(ans,middle);
left = middle+1;
}else {
right = middle;
}
}
System.out.println(ans);
}
//判断是否可以拼凑成x套牌
public static boolean check(int x) {
//记录下可用的空白牌
long m = M;
//遍历card,如果card[i] != x,那么先 判断可用牌upperLimit[i]是否能够凑到x
//如果可用凑到x,那么就减少m,如果m不足,也是不能拼接成功
for(int i = 1;i<=N;i++) {
//i卡牌肯定能凑出x套,所以跳过
if(card[i] >= x) continue;
//如果i卡牌加上可用卡牌还是小于x,肯定不能凑出x套
if(card[i]+upperLimit[i] < x) return false;
//用空白牌补充i卡牌,令其满足x张卡牌的最低标准
m -= Math.abs(x-card[i]);
//如果空白牌不够用了也是不能凑出x套
if(m < 0) return false;
}
return true;
}
}