原题链接:https://www.acwing.com/problem/content/1228/
题意解析
用N个数字凑出X
和一般背包相比,不同之处就在于必须正好凑到X,而不是求小于x的价值最大值
解析
首先我们要知道
原理1:
若这些数字的最大公约数不是1,则一定有无穷多个数字凑不出来
这里的凑就是指存在(x1,x2,x3,x4,x5,x6,……)使得A1x1+A2x2+A3x3+……=X
显然原理1是能凑出的必要不充分条件
证明:
举例:若gcd=3,则所有不能被三整除的数,都不可以被凑出来。
补充:这个原理可以看成裴蜀定理的推论
原理2:
gcd(a,b)=1,则最大不能被凑出的整数是ab-a-b=(a-1)(b-1)-1
证明不写了,就是先用反证法证明确实表示不出,之后证明比ab-a-b大的数都可以被表示。
这样就形成了我们的思路:
首先我们判断是否gcd==1.
辗转相除法
int gcd(int a, int b){
return b ? gcd(b,a%b) : a;
}
若gcd==1,之后就是要解出来 不能表示的数的数量.
我们已知了最大的不能表示的数字ab-a-b;也就是在1到ab-a-b之间求出不能表示的数
完全背包问题
dp数组用于记录1到ab-a-b之间每个数是否可以凑出,用bool数组即可
经验告诉我们:ab-a-b的值不会超过10000 (猜测可能最大99*98,超过复杂度就超了)
所以我们开dp:bool f[包子数种类][10000];
完全背包的特点就是状态重叠,所以可以从三重循环优化到二重(优化 or TLE)
维数优化一般可以不管,空间一般不会被卡,但是循环次数不优化大概率过不了的·
for (int i = 1; i <= n; ++i){
for (int j = 0; j <= 10000; ++j)
dp[i][j] = dp[i - 1][j] || (j >= a[i] ? dp[i][j - a[i]] : false);
}
最后遍历DP,输出答案
for (int j = 0; j <= 10000; ++j)
if (!dp[n][j]) ans++;
cout << ans;
}
AC代码(C++)
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 10010;
int a[110];
bool f[110][N];
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
int main()
{
int n;
scanf("%d", &n);
int d = 0;
for (int i = 1; i <= n; i ++ )
{
scanf("%d", &a[i]);
d = gcd(d, a[i]);
}
if (d != 1) puts("INF");
else
{
f[0][0] = true;
for (int i = 1; i <= n; i ++ )
for (int j = 0; j < N; j ++ )
{
f[i][j] = f[i - 1][j];
if (j >= a[i]) f[i][j] |= f[i][j - a[i]];
}
int res = 0;
for (int i = 0; i < N; i ++ )
if (!f[n][i])
res ++ ;
printf("%d\n", res);
}
return 0;
}