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
a、b的情况,结论是:若
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,y≥0),它们能组合得到的数是:
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
a,b是整数且
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
x、y,也就是说答案不是INF。
不过,
x
、
y
x、y
x、y的解可能是负整数,而本题要求
x
、
y
x、y
x、y是非负整数。
下面证明:当
c
c
c很大时,肯定有
x
、
y
x、y
x、y的非负整数解。
在博文线性丢番图方程中指出,当
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=y0−an
其中
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
a、b,得:
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=by0−abn
取
n
n
n是一个正整数,有
a
x
=
a
x
0
+
a
b
n
≥
0
ax =a x_0 + abn\geq0
ax=ax0+abn≥0,即
x
x
x是非负的。而:
b
y
=
c
−
a
x
0
−
a
b
n
by=c-ax_0-abn
by=c−ax0−abn
当
c
c
c很大时,
b
y
by
by也是非负的,即
y
y
y是非负的。
以上证明了
c
c
c很大时,存在
x
、
y
x、y
x、y的非负整数解。
以上讨论了给定2个数的情况,若给定多个数 a 、 b 、 e 、 f 、 a、b、e、f、 a、b、e、f、…可以推导出结论: 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、
a、b、e、…时,计算所有
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=a、2a、3a、......都算一遍。
b
、
e
、
b、e、
b、e、…也一样。
用
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))