原来货币系统的面额数组用a[1...n]
表示,简化后货币系统的面额数组用b[1...m]
表示,这里的最小m
即为题目要求我们求解出来的。
解法一:动态规划(完全背包模型)
从题目可分析得出三条性质:
(1) b[1...m]
的每个元素不能由其它剩余元素的任意组合表示出来,否则,这个元素可被删除掉;
(2) a[1...n]
的每个元素均可由b[1...m]
的某种组合表示出来,否则,这两个系统不满足题目所述的等价的含义;
(3) b[1...m]
的元素均来自a[1...n]
。可用反证法来证明,如下:假设存在数组b
中某元素值y
不属于数组a
,根据货币系统等价的定义,货币系统b
能表示出y
则货币系统a
也必然能表示出y
,也就是说y
可以写成a[1...n]
的元素的某种组合的形式,不失一般性地,假设可写成y=a[1]+a[2]+a[3]
,根据前面的性质(2),a[1]
、a[2]
、a[3]
可写成b[1...m]
的某种组合,则y
可写成b[1...m]
的某种组合,这与性质(1)矛盾。故性质(3)成立。
有了上述三条性质之后,可得出这样的结论:b[1...m]
由a[1...n]
中不能由其它剩余元素表示出来的元素值组成。
因此,本题的思路就是:对a[1...n]
排序,判断a[i]
是否能由a[1...(i-1)]
的某种组合表示出来(即完全背包问题,求方案数),若不能则保留,若能则删去,保留下来的即是b[1...m]
。
(1) 朴素写法
import java.util.*;
public class Main{
static int N = 110;
static int M = 25010;
static int[] a = new int[N];
static int[][] f = new int[N][M];
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int T = sc.nextInt();
while(T -- > 0){
int n = sc.nextInt();
for(int i = 1; i <= n; i ++) a[i] = sc.nextInt();
// 初始化
for(int i = 0; i < N; i ++)
for(int j = 0; j < M; j ++){
if(j == 0) f[i][j] = 1;
else f[i][j] = 0;
}
Arrays.sort(a, 1, n + 1);
int res = 0;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= a[n]; j ++){
if(j == a[i] && f[i - 1][j] == 0) res ++;
f[i][j] = f[i - 1][j];
if(j >= a[i]) f[i][j] += f[i][j - a[i]];
}
System.out.println(res);
}
}
}
(2) 空间优化后的写法
import java.util.*;
public class Main{
static int N = 110;
static int M = 25010;
static int[] a = new int[N];
static int[] f = new int[M];
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int T = sc.nextInt();
while(T -- > 0){
int n = sc.nextInt();
for(int i = 1; i <= n; i ++) a[i] = sc.nextInt();
Arrays.sort(a, 1, n + 1);
Arrays.fill(f, 0);
int res = 0;
f[0] = 1; // 初始化
for(int i = 1; i <= n; i ++){
if(f[a[i]] == 0) res ++;
for(int j = a[i]; j <= a[n]; j ++)
f[j] += f[j - a[i]];
}
System.out.println(res);
}
}
}