题意为:现有 a[1]、a[2]、a[3]...a[n]
这 n
种面额的纸币无穷多张,现在要找出 m
种面额为 b[1]、b[2]、b[3]...b[m]
的无穷多张纸币,使这两种货币系统所能表示出来的金额的范围相等,且 m
要尽可能地小。
思路为:我们设 b 货币系统
是 a 货币系统
的最优解,即 b[i]
一定不能被 b 系统
的任何金额所表示,否则就不是最优解。先写出此题的性质:
性质1:a 货币系统
所能表示的金额一定能被 b 货币系统
所表示。
性质2:最优解的定义,即 b[i]
不能被 b 系统
的金额表示出来。
性质3:b[i]
一定是从 a 系统
中选择出来的,即 b 系统
就是删掉几个 a[i]
后的 a 系统
。
用反证法证明性质3:假设 b[i]∉a,且根据题意 b 系统
的任何一个金额(包括b[i]
本身)都属于 a 系统
所能表示的金额范围。那么抽象地写出关系就是 b[i] = a[1] * t[i] + a[2] * t[j] + ... + a[n] * t[k]
,因为上面假设 b[i]∉a,那么 b[i] > a[1],a[2],...,a[n]
所以 b[i]
至少等于两个及以上 a 中的面额乘以若干数量之和。
又由 性质1 可以得出 a[1] * t[i] + a[2] * t[j] + ... + a[n] * t[k] = b[1] * t[i] + b[2] * t[j] + ... + b[n] * t[k]
这就等价于 b[i]
可以由 b 系统
的某种若干金额表示出来,这就于性质2相悖,那么 b系统
就不是最优解了。
故 b[i]∈a
那么在代码层面,我们的目的就变为了找出 a 系统
中不能被其他种类金额用若干数量表示出来的金额种数。那么怎样找出这种不能被其他数字表示出来的数呢?首先给 a 数组
排序,排序的目的是为了方便计算机能够以线性的方式找出这种数,因为排序之后,设当前处理的数是 a[i]
,a[i + 1 : n]
后面的数比它大,所以一定不能用后面的数以任意形式表示 a[i]
,我们就要看 a[1 : i - 1]
的数能否组成 a[i]
,不能组成则其一定是我们寻找的那种数,最后的结果是一种处理之前就能确定的结果,这种线性地处理过程中对于 a[i]
只有是这种数和不是这种数两种结果,没有其余可能的余地。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 110, M = 25010;
int money[N], dp[M];
int t, n;
int main() {
cin >> t;
while(t--) {
cin >> n;
int mx = 0;
for(int i = 1; i <= n; i++) cin >> money[i], mx = max(mx, money[i]);
sort(money, money + n + 1);
memset(dp, 0, sizeof dp);
dp[0] = 1;
int res = 0;
for(int i = 1; i <= n; i++) {
for(int j = money[i]; j <= mx; j++)
dp[j] = dp[j] + dp[j - money[i]];
}
for(int i = 1; i <= n; i++)
if(dp[money[i]] == 1) res++;
cout << res << endl;
}
return 0;
}
以上代码的逻辑是用滚动数组完全背包求方案数的模型,来求出以上说明的那种数的数量。dp[i][j]
的含义:用a[1 : i]
种金额能表示金额 j
的方案数,只不过滚动数组去掉了 i
的维度。
下面的逻辑是在递推完所有的金额种类在 1 - mx
金额的方案数后,如果 dp[i][j]
的方案数等于1,说明只有a[i]
能表示金额j
,这就是我们要找的结果之一,计数器++,直到遍历完输出结果即可。