zoukankan      html  css  js  c++  java
  • 【蓝桥杯】买不到的数目

    买不到的数目

    小明开了一家糖果店。他别出心裁:把水果糖包成4颗一包和7颗一包的两种。糖果不能拆包卖。
    小朋友来买糖的时候,他就用这两种包装来组合。当然有些糖果数目是无法组合出来的,比如要买 10 颗糖。
    你可以用计算机测试一下,在这种包装情况下,最大不能买到的数量是17。大于17的任何数字都可以用4和7组合出来。
    本题的要求就是在已知两个包装的数量时,求最大不能组合出的数字。
    输入:
    两个正整数,表示每种包装中糖的颗数(都不多于1000)
    要求输出:
    一个正整数,表示最大不能买到的糖数
    不需要考虑无解的情况
    例如:
    用户输入:
    4 7
    程序应该输出:
    17
    再例如:
    用户输入:
    3 5
    程序应该输出:
    7

    资源约定:
    峰值内存消耗 < 64M
    CPU消耗 < 3000ms

    观察题目,其实可以把题目抽象成:有两个正整数a、b,可以组成任意线性组合ax+by,x、y非负,求最大不能组合数。

    我们可以证明这两个数一定互质。设ax+by=c,如果a、b不互质,则gcd(a,b)≠0,a、b的线性组合一定是gcd(a,b)的倍数;当c的取值不是gcd(a,b)的倍数时,方程无解,此时a、b不能组合数无上界(只要不是gcd(a,b)的倍数都无法组合,这样的数有无数多个),所以a、b一定互质(即gcd(a,b)=1)

    法一:

    做这道题有一个结论可以直接用:两个互质数a、b的最大不能组合数为ab-a-b。下面给出详细证明(参考):

    如果有离散数学的基础,我们知道可以把所有非负整数划分成a个模a同余等价类,记[0],[1],[2],...,[a-1],分别为ak,ak+1,ak+2,…,ak+(a-1),(kZ)b的倍数一定分布在这a个等价类中,又因为gcd(a,b)=1,所以每个等价类中都有b的倍数,且均分分布(这边想不通可以自己取两个数比画一下,就很好理解了)。特别的,ab[0]bKi是等价类[i]中最小的b的倍数,那么该等价类中bKi后的所有数都能表示成ax+bKi,一定能被组合出来。显然,最大的bKi后面的所有连续整数都可以被组合出来,而要找到最大不能组合数,我们只需考虑最大的bKi前面的数字即可。把所有的bKi列出来,有{0,b,2b,...,(a-1)b}。很显然(a-1)b就是最大的bKi,而在其他a-1个等价类中,必定有比它小且能被组合的数,这些数就是(a-1)b-1,(a-1)b-2,...,(a-1)b-(a-1)。所以最大不能被组合数就是(a-1)b-a,即ab-a-b。

    为了便于理解,我以4、7为例画了个图表,结合图表应该很容易就能看懂上面的证明过程:

    知道了这个结论,这道题就很好做了,几乎是水过:

     1 #include<iostream>
     2 using namespace std;
     3 
     4 int main(){
     5     int a,b;
     6     cin>>a>>b;
     7     cout<<a*b-a-b<<endl; 
     8         
     9     return 0;
    10 }
    View Code

    法二

    但是肯定很多人都不知道有这个结论呀,那么我们可以用枚举来做。

    由上可知这个最大不能组合数一定是不会超过ab的,当然比赛的时候我们不知道也可以猜,这个不能组合数一定不会特别大,而最好猜的就是ab。把ab内所有可能的解枚举出来,然后从后往前找不能被组合出来的数,那么第一个找到的数就是最大不能组合数。(顺便插一句,c++里的set是有序的,差点忘了。。。)

     1 #include<iostream>
     2 #include <set>
     3 using namespace std;
     4 
     5 int main(){
     6     set<int> s;
     7     set<int>::iterator it;
     8     int a,b;
     9     cin>>a>>b;
    10     for(int x = 0;a*x<=a*b;x++)  //枚举a*b以内的所有解 
    11         for(int y = 0;a*x+b*y<=a*b;y++) 
    12             s.insert(a*x+b*y);
    13     for(int i = a*b; i>=0; i--){
    14         if(s.find(i)==s.end()) {  //i不在set中,那么i就是答案
    15             cout<<i<<endl;
    16             break;   //找到后跳出循环            
    17         }
    18     }
    19 
    20     return 0;
    21 }

    法三

    用动规做,其实也是枚举,如下是参考别人的代码,原文链接戳这里

     1 #include<iostream>
     2 using namespace std;
     3 
     4 int main()
     5 {
     6     int t=0,n,m;
     7     cin>>n>>m;
     8     int a[100005];
     9     a[0]=1;
    10     for(int i=1;i<=n*m;i++){
    11         if(i>=n&&a[i-n])
    12             a[i]=1;
    13         else if(i>=m&&a[i-m])
    14             a[i]=1;
    15     }
    16     for(int i=n*m;i>=0;i--)
    17     {
    18         if(!a[i]){
    19             cout<<i<<endl;
    20             break;
    21         }
    22     }
    23     
    24     return 0;
    25 }

    解释一下这段代码,能被标记成1的就相当于是能被组合出来的数,起始让a[0]为1,这样在循环的时候就可以先把n和m标记成1,这两个数也确实是可被m、n线性组合出来的(让另一个系数为0);如果一个数i被标记成1,那么i+n、i+m也可以被组合出来,所以也标记上1。循环的边界同样是m*n。

    还看到一种,和这个差不多,用了清新脱俗的图表填充归纳法(瞎起的名字),可能更好理解,链接:这里这里

    内容(以下为来自原文的引用):

    对于4 7

      

      4与7,大周期为7,小周期为4,当前层数能被填充的下一层对应的格子也能被填充。

      开始位置为0 / 4,然后是4与7的周期差

      在7*4后完成填充,最后填充的数字为7 * 4 – 4。

      最大未被填充的数字在上一层,即 7*4 – 4 – 7。

      替换字母后得到公式 a*b – a – b。

    【代码 C++】

     1 #include <cstdio>
     2 #define mx 1000005
     3 int data[mx];
     4 int main(){
     5     int i, j, a, b;
     6     scanf("%d%d", &a, &b);
     7     data[a] = data[b] = 1;
     8     for (i = a + b / 2; i <= a*b; ++i){
     9         if (data[i - a]) ++data[i];
    10         if (data[i - b]) ++data[i];
    11     }
    12     for (i = a*b; data[i]; --i);
    13     printf("%d", i);
    14     return 0;
    15 }
    View Code

    另一个,思路和上面都一样,标准dp,令dp[i] = 1表示i能够由n和m组成,否则不能,则有dp[i] = dp[i-n]||dp[i-m] ? 1 : 0;

    原文这里

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 #include <cmath>
     5 using namespace std;
     6  
     7 const int maxn = 1000005;
     8 int dp[maxn];
     9  
    10 int main() {
    11     int n, m;
    12     while(scanf("%d %d", &n, &m) != EOF) {
    13         memset(dp, 0, sizeof(dp));
    14         dp[n] = 1, dp[m] = 1;
    15         int t = max(n, m);
    16         for(int i = t + 1; i < maxn; i++) {
    17             if(dp[i - n] || dp[i - m]) dp[i] = 1;
    18         }
    19         
    20         for(int i = maxn - 1; i >= 0; i--) {
    21             if(!dp[i]) {
    22                 printf("%d
    ", i);
    23                 break;
    24             }
    25         }
    26     }
    27     return 0;
    28 }
    View Code
  • 相关阅读:
    centos安装杂记inittabhostnamessh
    centos6安装aircrack,reaver1.4
    20175236 201820192 《Java程序设计》第五周学习总结
    20175236 201820192 《Java程序设计》第三周学习总结
    20175236 JAVA MyCP(课下作业)
    20175236 201820192 《Java程序设计》第六周学习总结
    小学生之Java中的异常
    小学生之面向对象的三个特征继承、封装、多态
    小学生之类与对象
    小学生之手(01)之 "for循环"
  • 原文地址:https://www.cnblogs.com/Aikoin/p/10504719.html
Copyright © 2011-2022 走看看