一、题目解析
感觉这道题重点不是 \(DP\) 而是证明……很多高赞题解都是直接上结论的……,学习算法,个人感觉还是把理论基础打牢才靠谱
比如你考场的时候,想到了这个解法,如果用了怕结论是错的,得分还不如部分分;不用又怕错失正解,其实是很纠结的……所以我这里就写一写证明过程吧,其实也不难。
Statement
有 nn 种不同面值的货币,第 ii 种面值为 a[i]a[i] ,每种无限张。
将一个有 nn 种货币、面值数组为 a[1…n]a[1…n] 的货币系统记作 (n,a)(n,a) .称两个货币系统 (n,a),(m,b)(n,a),(m,b) 等价,当且仅当对于任意 x∈Nx∈N ,要么均能被两个货币系统表示出来,要么不能被任何一个表示。
给定一个货币系统 (n,a)(n,a) ,找到一个最小的 mm 使得存在货币系统 (m,b)(m,b) 与 (n,a)(n,a) 等价。
n≤100,a[i]≤25000n≤100,a[i]≤25000 .
#include <bits/stdc++.h>
using namespace std;
const int N = 110; //N个正整数
const int M = 25010; //表示的最大金额上限
int n; //实际输入的正整数个数
int v[N]; //每个输入的数字,也相当于占用的体积是多大
int f[M]; //二维优化为一维的DP数组,f[i]:面额为i时的前序货币组成方案数
int main() {
int T;
cin >> T;
while (T--) {
cin >> n;
for (int i = 0; i < n; i++)cin >> v[i];
//每个货币的金额,都只能由比它小的货币组装而成,需要排一下序
sort(v, v + n);
//背包容量
int m = v[n - 1];
//每轮初始化一次dp数组,因为有多轮
memset(f, 0, sizeof f);
//在总金额是0的情况下,只有一种方案
f[0] = 1;
//利用01背包计算每个体积(面额)的组成方案
for (int i = 0; i < n; i++)
for (int j = v[i]; j <= m; j++)
f[j] += f[j - v[i]];
//统计结果数
int res = 0;
for (int i = 0; i < n; i++)
//如果当前面额的组成方案只有一种,那么它只能被用自己描述自己,不能让其它人描述自己
//这个面额就必须保留
if (f[v[i]] == 1)res++;
//输出结果
printf("%d\n", res);
}
}