题目来源:蓝桥杯-2017

题目

小明几乎每天早晨都会在一家包子铺吃早餐。这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子
每种蒸笼都有非常多笼,可以认为是无限笼。
每当有顾客想买X个包子,卖包子的大叔就会选出若干笼包子来,使得这若干笼中恰好一共有X个包子。
比如一共有3种蒸笼,分别能放3、4和5个包子。当顾客想买11个包子时,大叔就会选2笼3个的再加1笼5个的(也可能选出1笼3个的再加2笼4个的)。
当然有时包子大叔无论如何也凑不出顾客想买的数量。
比如一共有3种蒸笼,分别能放4、5和6个包子。而顾客想买7个包子时,大叔就凑不出来了。
小明想知道一共有多少种数目是包子大叔凑不出来的。

输入
第一行包含一个整数N。(1 <= N <= 100)
以下N行每行包含一个整数Ai。(1 <= Ai <= 100)

输出
输出一行包含一个整数代表答案。如果凑不出的数目有无限多个,输出INF。

示例

输入:

2
4
5

输出:

6

输入:

2
4
6

输出:

INF

解题思路

​ 本题的难点在于如何判断不能够组成的数字是否有无限个。下面首先进行推导什么情况下是无限个,已经知道原因的读者可以下划到分割线处直接看做题思路。

​ 拿两个示例举例,4和5不能组成的数只有6个:1,2,3,6,7,11。4和6不能组成的数是2和全体奇数

​ 从4和6两个数入手,我们就能知道什么时候不能组成的数字有无限个了:6和4都是2的倍数,那么几乎所有2的倍数都能用4和6表示,此时所有奇数都无法表示。也就是说如果两个数的最大公因数不是1的情况下,他们能够组成的数字其实都是最大公因数的倍数,例如4和6能组成的数都是2的倍数。那么此时只要取任意一个非2的倍数的数字,用4和6都必然不能表示。

​ 再去反观4和5两个数字,他们的最大公因数是1,那么他们是否就能组成所有的数字了呢?我们发现除掉1、2、3、6、7、11之外,所有的偶数好像都能用4的倍数表示,倘若一个数不能把4整除,那么我们可以留出一部分数让5来处理。拿10举例,10是偶数但是10不能被4整除,2*4=8,3*4=12,他们之间的差值为4,此时我们可以用2*5=10弥补中间的插值2,这样一来可以用a*4+b*5构成几乎所有的偶数。同理,可以用a*5+b*4构成几乎所有的奇数。那么用x*4+y*5就能构成几乎所有的数,此时不能构成的数是有限个。

​ 也就是说,无论给我们什么样的数据,如果他们中的部分数字彼此存在一个不为1的最大公因数,那么这几个数字其实就是这个最大公因数的倍数,中间必定存在无法表示的数字。

​ 综上,如果给我们的数彼此之间的最大公因数是1,也就是这些数字彼此互质,那么不能构成的数字就是有限个,反之就是无限个。


​ 首先我们需要编写一个函数用来判断输入的数据彼此间是否互质。这里我们采用的是欧几里得算法,也就是辗转相除法

​ 不了解该算法的可以查看示例,了解该算法的运行原理,或者点击链接访问欧几里得算法-百度百科。

假如需要求 1997 和 615 两个正整数的最大公约数,用欧几里得算法,是这样进行的:
1997 / 615 = 3 (余 152)
615 / 152 = 4(余7)
152 / 7 = 21(余5)
7 / 5 = 1 (余2)
5 / 2 = 2 (余1)
2 / 1 = 2 (余0)
至此,最大公约数为1
以除数和余数反复做除法运算,当余数为 0 时,取当前算式除数为最大公约数,所以就得出了 1997 和 615 的最大公约数 1。

public static int gcd(int a, int b) {return b == 0 ? a : gcd(b, a % b);
}

​ 编写一个启动函数,将数据预处理

public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();int[] input = new int[n];for (int i = 0; i < n; i++)input[i] = scanner.nextInt();get(input);
}

​ 下面是个人认为非常容易理解的代码,该代码的执行效率非常低(至少运行起来很慢,肉眼可见的慢),在理解之后可以大幅度优化。

​ 本代码解释会利用输入数据2 4 5进行阐述。首先定义一个gcd,用来表示输入的数据彼此是否互质,由于每次计算都会刷新gcd的值,所以只有所有数据彼此之间互质的时候,gcd才会一直为1。 利用gcd可以直接确定是否输出INF。

​ 如果不输出INF,也就是无法表示的个数有限,那么我们就先创建一个list用来存储所有的输入的数据。然后创建一个res,用来存放无法表示的数字。首先利用for循环把所有数据添加到list中,如4,5。然后我们设置一个阈值,遍历这个范围所有的数,检测能否用list中的数据表示。

​ 首先定义一个flag=false,用来表示当前处理的数字i能否用list中的数字组成。如果能组成,那就让flag=true,同时把这个数字添加到list中。处理能否组成的方法是判断数字自减能否组成,例如我们判断数字6时,先判断0和6是否都在list中,然后判断1和5,2和4,3和3。其中只要有一组判断成功,就代表数字可以组成,本题是遍历了这4种情况之后,仍然没有找到处理方案,所以6是不能构成的数,反之,如果能够构成的话,就由flag=true结束循环,同时将值添加到list中,便于后续的计算。循环结束,利用flag=false将6添加到res中。直到遍历完整个循环,由于所有不能构成的数字我们都add到了res中,所以直接输出res的size即可。详细流程请查看代码。

public static void get(int[] input) {//gcd初始值int gcd = input[0];//判断所有输入的数据彼此是否互质for (int i = 0; i < input.length - 1; i++)gcd = gcd(gcd, input[i + 1]);//如果不互质,输出INF,否则进入if (gcd == 1) {//用来存放所有能够组成的数字,后续可以利用//因为 list.contains(4) && list.contains(5) , 4+5=9//所以 list.add(9)//这种思想迅速的判断能够组成的数List<Integer> list = new ArrayList<>();//用来存放不能组成的数字List<Integer> res = new ArrayList<>();//输入的数据自然都是能够组成的,直接add即可for (int i = 0; i < input.length; i++)list.add(input[i]);//遍历到阈值10000for (int i = 1; i <= 10000; i++) {//标识符boolean flag = false;//flag只有在能够组成时才会改变,而能够组成的话,循环就可以停止了,所以这里添加了!flag的判断for (int j = 0; j <= i / 2 && !flag; j++)//这里我们不考虑特殊值0的问题,实际上例如判断4的时候,虽然没有0,但是4切实存在,所以直接添加了 ||list.contains 条件if ((list.contains(j) && list.contains(i - j)) || list.contains(i)) {//如果list中存在i,就不用再添加i了if (!list.contains(i))list.add(i);//标识符更改,表示循环结束,也表示该数字可以组成flag = true;}//根据flag的值,选择将i添加到res中if (!flag)res.add(i);}//res的大小就是不能组成的数字个数System.out.println(res.size());} elseSystem.out.println("INF");
}

​ 我们发现,之所以使用list,其实就是为了使用他的contains方法,但是这样大量的消耗了时间资源,每一次都要判断是否contains,无疑带来了很大的时间损失,而实际上我们可以写一个bool数组,利用下标当作每一个数字用下标对应的true/false来确定能否组成。这样一来,list.contains(4)就可以写成bool[4]了。同样的,题目并未让我们给出所有不可组成的值,所以我们只需要定义一个count计数器,每次遇见不能组成的数字就加1就可以了。这样一来代码就可以写成这个样子(改写的部分已附加注释)

public static void get(int[] input) {int gcd = input[0];for (int i = 0; i < input.length - 1; i++)gcd = gcd(gcd, input[i + 1]);//定义计数器,不再创建res对不可组成的数字进行收集int count = 0;if (gcd == 1) {//阈值是10000,为了利用下标,创建到10001,默认均为falseboolean[] isGet = new boolean[10001];//输入的数据能组成,也就是truefor (int i = 0; i < input.length; i++)isGet[input[i]] = true;//前边的代码没有考虑到0,这里考虑到0。既确保了数组每个值都有实际意义,也确保了例如 0和4 isGet[0] = true;for (int i = 1; i <= 10000; i++) {boolean flag = false;for (int j = 0; j <= i / 2 && !flag; j++)//已经考虑过0的情况,所以或条件删除掉,上文也提到了contains可以用数组下标代替if (isGet[j] && isGet[i - j])//让下标对应值改为true的同时,也让flag发生变化isGet[i] = flag = true;if (!flag)count++;}}//输出语句整合成一条,其实也可以不用这么写,根据if语句分开写的可读性更高System.out.println(gcd == 1 ? count : "INF");
}

代码实现

import java.util.Scanner;public class bao_zi_cou_shu {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();int[] input = new int[n];for (int i = 0; i < n; i++)input[i] = scanner.nextInt();get(input);}public static void get(int[] input) {int gcd = input[0];for (int i = 0; i < input.length - 1; i++)gcd = gcd(gcd, input[i + 1]);int count = 0;if (gcd == 1) {boolean[] isGet = new boolean[10001];for (int i = 0; i < input.length; i++)isGet[input[i]] = true;isGet[0] = true;for (int i = 1; i <= 10000; i++) {boolean flag = false;for (int j = 0; j <= i / 2 && !flag; j++)if (isGet[j] && isGet[i - j])isGet[i] = flag = true;if (!flag)count++;}}System.out.println(gcd == 1 ? count : "INF");}public static int gcd(int a, int b) {return b == 0 ? a : gcd(b, a % b);}
}

本题无执行用时和内存消耗统计

【蓝桥杯】【2017】【包子问题】非常容易理解的欧几里得算法相关推荐

  1. 蓝桥杯2017 包子凑数

    小明几乎每天早晨都会在一家包子铺吃早餐.他发现这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子.每种蒸笼都有非常多笼,可以认为是无限笼. 每当有顾客想买X个包子,卖包子的大叔就会迅速选出若干笼包 ...

  2. [蓝桥杯][2017年第八届真题]包子凑数(解题报告)

    问题 1886: [蓝桥杯][2017年第八届真题]包子凑数 时间限制: 1Sec 内存限制: 128MB 提交: 406 解决: 118 题目描述 小明几乎每天早晨都会在一家包子铺吃早餐.他发现这家 ...

  3. 第八届蓝桥杯 2017年省赛真题(Java 大学C组)

    蓝桥杯 2017年省赛真题 (Java 大学C组 ) 第一题:外星日历 第二题:兴趣小组 第三题:纸牌三角形 第四题:承压计算 第五题:杨辉三角 第六题:最大公共子串 第七题:Excel地址 第八题: ...

  4. [蓝桥杯2017初赛]跳蚱蜢-map标记+bfs+环形数组

    解题思路: 这题如果我们考虑蚱蜢跳,有很多蚱蜢,有很多情况,所以我们让空盘跳,这样就简化题目了,然后我们化圆为直,将题目的情况看成字符串012345678,最后要变成087654321,这样题目就变得 ...

  5. 蓝桥杯2017初赛:迷宫 (dfs搜索)

    [蓝桥杯2017初赛]迷宫 Description X星球的一处迷宫游乐场建在某个小山坡上.它是由10x10相互连通的小房间组成的. 房间的地板上写着一个很大的字母.我们假设玩家是面朝上坡的方向站立, ...

  6. 题目 1886: 蓝桥杯2017年第八届真题-包子凑数

    时间限制: 1Sec 内存限制: 128MB 提交: 2378 解决: 789 题目描述 小明几乎每天早晨都会在一家包子铺吃早餐.他发现这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子.每种蒸笼 ...

  7. 蓝桥杯2017年第八届C/C++ B组省赛习题题解

    目录 第一题:购物单(暴力计算) 第二题:等差素数数列(数学+暴力枚举) 第三题:承压计算(模拟) 第四题:方格分割(dfs) 第五题:取数位(模拟) 第六题:最大公共子串(dp) 第七题:日期问题( ...

  8. 渣科的第一次蓝桥杯2017

    2017/4/7 出发的前一天, 大约下午1:30,开始整理东西,还是决定带上电脑,书包好重,原来买的三罐八宝粥我就不带了,最后选择了三条士力架,一条给亮,一条给舍友,一条给自己,带上衣服裤子,收拾完 ...

  9. 【解题报告+通法】_九宫幻方 蓝桥杯 2017年C组第八题(dfs解法)

    题目描述 小明最近在教邻居家的小朋友小学奥数,而最近正好讲述到了三阶幻方这个部分,三阶幻方指的是将1~9不重复的填入一个3*3的矩阵当中,使得每一行.每一列和每一条对角线的和都是相同的. 三阶幻方又被 ...

最新文章

  1. Linux下 curl 代理设置注意事项--curl proxy
  2. Android ExpandableListView几个特殊的属性
  3. 提高solr的搜索速度
  4. 浏览器兼容之旅的第二站:各浏览器的Hack写法
  5. Jmeter高阶学习,运用NotePad++编写工程,随意复制多个工程到同一个工程
  6. 阿里P8手把手教你!java私塾培训
  7. 时空轨迹数据挖掘综述
  8. AR涂涂乐⭐三、 C#实现识别图进入扫描框显示绿色,未进入为红色功能
  9. 那些年常见的前端bug (持续更新)
  10. 【MySQL5.7指南】第一章——概述
  11. 趣谈win10常用快捷键
  12. 电视机未来会成为家庭交互中心?
  13. Linux如何查看当前Ubuntu系统的版本
  14. Instruments性能检测
  15. python矩阵的切片操作
  16. 3朵红花=60,1朵红花+2朵蓝花=30,1朵蓝花-两朵黄花=3,1朵黄花+1朵红花+1朵蓝花=?...
  17. Matlab验算拉格朗日中值定理
  18. 洛谷 P1506 拯救oibh总部 题解(洪水填充法的模板)
  19. 手持式无线电综合测试仪----- TFN PM1200
  20. AWZ爱伪装详细使用教程

热门文章

  1. 高效真实的云效果渲染算法
  2. 尊重兴趣,是尊重生命深处的本能
  3. 实战:打造最强王者云笔记及个人知识管理库-Obsidian+Typora+坚果云-2022.5.15
  4. Xshell7家庭版
  5. 推陈、致新——城市更新促进美丽城市建设
  6. 创业之初需要的是人而不是钱
  7. vue3+ts+vue-qr 使用canvas生成彩色实点二维码
  8. Qt5生成exe文件更改图标
  9. 安装 totem-pps 看网络电视
  10. c# 软件皮肤自定义