倪文迪陪你学蓝桥杯2021寒假每日一题:1.10日(2017省赛A第8题)

2021年寒假每日一题,2017~2019年的省赛真题。
本文内容由倪文迪(华东理工大学计算机系软件192班)和罗勇军老师提供。

后面的每日一题,每题发一个新博文,请大家看博客目录https://blog.csdn.net/weixin_43914593

2017省赛A类第8题,题目链接:
包子凑数 http://oj.ecustacm.cn/problem.php?id=1322

1、题目描述


  给出N个整数,每个整数有无限多个。任取一些整数相加,得到大于等于1的数。问有多少个数得不到。如果得不到的有无限多个,输出“INF”;否则输出得不到的数量。
  例如给出4、5,它们组合不能得到的数有6个:1, 2, 3, 6, 7, 11。其他的数都能通过4,5组合得到。


看下面的题解前请先自己编码求解。

2、题解

  倪文迪的话:“本题为DP,同时考察了素数、最大公约数的性质。如果给出的n个数的最大公约数不为1,必定恒有数字不被覆盖,这一点可以用来判断结果是否为INF。剩下的问题就是n个数的背包,通过状态转移求解即可。”
  罗勇军老师的话:题目分两步,(1)判断结果是否为INF;(2)如果不是INF,统计数量。考点是“数论gcd+简单DP”。

2.1 什么时候答案不是INF

  什么时候答案不是INF?也就是说,除了少数一些整数无法组合得到,其他所有的整数都能得到。
  首先看2个数 a 、 b a、b ab的情况,结论是:若 g c d ( a , b ) = 1 gcd(a, b) = 1 gcd(a,b)=1,则答案不是INF。

  以两个数4、5为例,任取 x x x个4和 y y y个5( x , y ≥ 0 x,y\geq0 x,y0),它们能组合得到的数是:
   4 x + 5 y = c 4x+5y=c 4x+5y=c
  若把 c c c看成常数,这是一个二元一次方程。
  关于二元一次方程(又称为二元线性丢番图方程),请阅读这篇博文:线性丢番图方程
  博文给出了二元一次方程 a x + b y = c ax + by = c ax+by=c有整数解的定理:设 a , b a,b ab是整数且 g c d ( a , b ) = d gcd(a, b) = d gcd(a,b)=d,如果 d d d不能整除 c c c,那么方程 a x + b y = c ax + by = c ax+by=c没有整数解,如果 d d d能整除 c c c,那么存在无穷多个整数解。
  显然,如果 g c d ( a , b ) = 1 gcd(a, b) = 1 gcd(a,b)=1,由于1能整除所有整数,此时 a x + b y ax + by ax+by能得到所有整数。
  在例子 4 x + 5 y = c 4x+5y=c 4x+5y=c中,因为 g c d ( 4 , 5 ) = 1 gcd(4, 5) = 1 gcd(4,5)=1,那么不管 c c c是什么整数,都存在整数解 x 、 y x、y xy,也就是说答案不是INF。
  不过, x 、 y x、y xy的解可能是负整数,而本题要求 x 、 y x、y xy是非负整数。
  下面证明:当 c c c很大时,肯定有 x 、 y x、y xy的非负整数解。
  在博文线性丢番图方程中指出,当 g c d ( a , b ) = 1 gcd(a, b) = 1 gcd(a,b)=1时, a x + b y = c ax + by = c ax+by=c的通解是:
   x = x 0 + b n x = x_0 + bn x=x0+bn
   y = y 0 − a n y = y_0 - an y=y0an
  其中 n n n是任意整数,而 x 0 x_0 x0 y 0 y_0 y0是一个特解,它显然满足: a x 0 + b y 0 = c ax_0 + by_0 = c ax0+by0=c.
  两式分别乘以 a 、 b a、b ab,得:
   a x = a x 0 + a b n ax =a x_0 + abn ax=ax0+abn
   b y = b y 0 − a b n by = by_0 - abn by=by0abn
  取 n n n是一个正整数,有 a x = a x 0 + a b n ≥ 0 ax =a x_0 + abn\geq0 ax=ax0+abn0,即 x x x是非负的。而:
   b y = c − a x 0 − a b n by=c-ax_0-abn by=cax0abn
  当 c c c很大时, b y by by也是非负的,即 y y y是非负的。
  以上证明了 c c c很大时,存在 x 、 y x、y xy的非负整数解。

  以上讨论了给定2个数的情况,若给定多个数 a 、 b 、 e 、 f 、 a、b、e、f、 abef…可以推导出结论: g c d ( a , b , e , f , . . . ) = 1 gcd(a,b,e,f,...)=1 gcd(a,b,e,f,...)=1时,答案是非INF。

2.2 统计

  给定多个数 a 、 b 、 e 、 a、b、e、 abe…时,计算所有 a x + b y + e z + . . . ax + by +ez+... ax+by+ez+...的值,最后统计出没有被计算出的整数的数量即可。
  编码很简单。例如 a a a,把它所有的倍数 i = a 、 2 a 、 3 a 、 . . . . . . i=a、2a、3a、...... i=a2a3a......都算一遍。 b 、 e 、 b、e、 be…也一样。
  用 d p [ i ] = 1 dp[i]=1 dp[i]=1表示第 i i i个整数被计算出来了,最后统计没有被算过的 d p [ i ] dp[i] dp[i]

3、C++代码

  OJ运行时间4ms

//newoj User: ln2037
#include<bits/stdc++.h>
using namespace std;
const int maxn = 13000;
int a[maxn];
int dp[maxn]={
    
    0};

int main() {
    
    
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    int g = a[1];
    for(int i = 2; i <= n; i++)  //计算所有数的gcd
        g = __gcd(g, a[i]);
    if(g != 1) {
    
    
        cout << "INF";
        return 0;
    }
    for(int i = 1; i <= n; i++) {
    
      //dp[i]:第i个整数是否被计算出来
        dp[a[i]]= 1;
        for(int j = 0; j + a[i] < 10000; j++)
            if(dp[j]) {
    
    
                dp[j + a[i]] = 1;
            }
    }
    int ans = 0;
    for(int i = 1; i < 10000; i++)
        if(dp[i] == 0)  
            ans++;
    cout << ans;
    return 0;
}

4、Java代码

  OJ上运行时间1.4s

//newoj User: __admin
import java.util.Scanner;

public class Main{
    
    
    public static int gcd(int a, int b) {
    
    
        if(b == 0)  return a;
        else return gcd(b, a%b);
    }

    public static void main(String args[]) {
    
    
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int[] arr = new int[105];

        arr[1] = in.nextInt();
        int d = arr[1];
        for(int i = 2; i <= n; i++)        {
    
    
            arr[i] = in.nextInt();
            d = gcd(d, arr[i]);
        }

        if(d != 1)
            System.out.print("INF");
        else {
    
    
            long dp[] = new long[10005];
            dp[0] = 1;

            for(int i = 1; i <= n; i++)
                for(int j = arr[i]; j <= 10000; j++)
                    dp[j] += dp[j-arr[i]];

            int res = 0;
            for(int i = 1; i <= 10000; i++)
                if(dp[i] == 0)
                    res ++;
            System.out.print(res+"\n");
        }
    }
}

5、Python代码

  OJ运行时间0.7s。注意看为什么Python代码这么短。

#new oj User: 20192031003
def gcd(a,b):
    if b==0:return a
    else:return gcd(b,a%b)
 
n=int(input())
numlist=[]
for i in range(n):
    numlist.append(int(input()))
gcdnum=numlist[0]
for i in range(1,n):
    gcdnum=gcd(gcdnum,numlist[i])
if gcdnum!=1:
    print("INF")
    exit()
baozinum=[0]*10001
baozinum[0]=1
for num in numlist:
    for i in range(0,10001):
        if baozinum[i]==1 and i+num<=10000:
            baozinum[i+num]=1
print(10001-sum(baozinum))

猜你喜欢

转载自blog.csdn.net/weixin_43914593/article/details/112405425
今日推荐