题目链接:
在网友的国度中共有 n 种不同面额的货币,第 i 种货币的面额为 a[i],你可以假设每一种货币都有无穷多张。 为了方便,我们把货币种数为 n、面额数组为 a[1..n] 的货币系统记作 (n,a)。 在一个完善的货币系统中,每一个非负整数的金额 x 都应该可以被表示出,即对每一个非负整数 x,都存在 n 个非负整数 t[i] 满足 a[i]×t[i] 的和为 x。 然而,在网友的国度中,货币系统可能是不完善的,即可能存在金额 x 不能被该货币系统表示出。 例如在货币系统 n=3, a=[2,5,9] 中,金额 1,3 就无法被表示出来。 两个货币系统 (n,a) 和 (m,b) 是等价的,当且仅当对于任意非负整数 x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。 现在网友们打算简化一下货币系统。 他们希望找到一个货币系统 (m,b),满足 (m,b) 与原来的货币系统 (n,a) 等价,且 m 尽可能的小。 他们希望你来协助完成这个艰巨的任务:找到最小的 m。 输入格式 输入文件的第一行包含一个整数 T,表示数据的组数。 接下来按照如下格式分别给出 T 组数据。 每组数据的第一行包含一个正整数 n。 接下来一行包含 n 个由空格隔开的正整数 a[i]。 输出格式 输出文件共有 T 行,对于每组数据,输出一行一个正整数,表示所有与 (n,a) 等价的货币系统 (m,b) 中,最小的 m。 数据范围 1≤n≤100, 1≤a[i]≤25000, 1≤T≤20 输入样例: 2 4 3 19 10 6 5 11 29 13 19 17 输出样例: 2 5
解题思路:
首先我们先明确2个性质:
性质1:对于每一个 b[i] 它不能由 b[0] ~~ b[i - 1] 拼凑出来
性质2:(m, b) 中的每一个数字都是从(n, a) 中选择的。
第一个性质很好理解, 我们来解释一下第二个性质:
假设b[i]由a[j - 1], a[j]拼出,即b[j] = a[j - 1] * k1 + a[j] * k2, K1, K2为正整数(不包括0),
若b[i]不在(n, a)中,则上式可以写成b[j] = a[j - 1] * k1 + a[j] * k2 = b[1] + b[2] + b[3](假设由这三个拼成)则与性质1矛盾,所以性质2成立:
解题步骤:
将从小到大(n,a)从小到大排序,当枚举到a[i]时判断a[i]是否能由a[0] ~~ a[i - 1](里面的任意一个数可选择任意多次)拼出,若能拼出则不选,否则说明可以选择该数。即转化为了完全背包问题的模板:
代码如下:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 110, M = 25010; int n; int v[N]; int f[N][M]; int main() { int T; cin >> T; while (T -- ) { cin >> n; for (int i = 1; i <= n; i ++ ) cin >> v[i]; int res = 0; memset(f, 0, sizeof f); sort(v + 1, v + n + 1); f[0][0] = 1; for (int i = 1; i <= n; i ++ ) { if (!f[i - 1][v[i]]) res ++ ; for (int j = 0; j <= v[n]; j ++ ) { for (int k = 0; k * v[i] <= j; k ++ ) { f[i][j] += f[i - 1][j - v[i] * k]; } } } cout << res << endl; } return 0; }
优化后:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 110, M = 25010; int n; int v[N]; int f[N][M]; int main() { int T; cin >> T; while (T -- ) { cin >> n; for (int i = 1; i <= n; i ++ ) cin >> v[i]; sort(v + 1, v + n + 1); memset(f, 0, sizeof f); f[0][0] = 1; int res = 0; for (int i = 1; i <= n; i ++ ) { if (!f[i - 1][v[i]]) res ++ ; for (int j = 0; j <= v[n]; j ++ ) { f[i][j] = f[i - 1][j]; if (j >= v[i]) f[i][j] += f[i][j - v[i]]; } } cout << res << endl; } return 0; }
再次优化后,完全背包问题最终版:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 110, M = 25010; int n; int v[N]; int f[M]; int main() { int T; cin >> T; while (T -- ) { cin >> n; for (int i = 1; i <= n; i ++ ) cin >> v[i]; sort(v + 1, v + n + 1); memset(f, 0, sizeof f); f[0] = 1; int res = 0; for (int i = 1; i <= n; i ++ ) { if (!f[v[i]]) res ++ ; for (int j = v[i]; j <= v[n]; j ++ ) f[j] += f[j - v[i]]; } cout << res << endl; } return 0; }