给定 V 种货币(单位:元),每种货币使用的次数不限。
不同种类的货币,面值可能是相同的。
现在,要你用这 V 种货币凑出 N 元钱,请问共有多少种不同的凑法。
输入格式
第一行包含两个整数 V 和 N。接下来的若干行,将一共输出 V 个整数,每个整数表示一种货币的面值。
输出格式
输出一个整数,表示所求总方案数。
数据范围
1 ≤ V ≤ 25,
1 ≤ N ≤ 10000
输入样例:
3 10
1 2 5
输出样例:
10
背包问题原型
- 状态表示:
f[i][j]
表示 从前i种货币中选,且总价值恰好为j的所有选法集合的方案数。 - 那么
f[n][m]
就表示表示 从前n
种货币中选,且总价值恰好为m的所有选法集合的方案数,即为答案。 - 集合划分:按照第i种货币可以选 0个,1个,2个,3个,,,,k个划分集合
f[i][j]
。其中k*w[i] <= j
,也就是说在背包能装下的情况下,枚举第i种货币可以选择几个。 - 状态计算:
f[i][j] = f[i-1][j] + f[i-1][j-w[i]] + f[i-1][j-2*w[i]] + ...... + f[i-1][j-k*w[i]] .
import java.util.Scanner;
public class MonetarySystem {
public static void main(String[] args) {
int N = 30, M = 10010;
long[][] f = new long[N][M];
int[] w = new int[N];
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
for (int i = 1; i <= n; i ++ ) w[i] = sc.nextInt();
f[0][0] = 1;//前0种货币取0元也是一种选法
for (int i = 1; i <= n; i ++ ) {
for (int j = 0; j <= m; j ++ ) {
for (int k = 0; k * w[i] <= j; k ++ ) {
f[i][j] += f[i - 1][j - k * w[i]];
}
}
}
System.out.println(f[n][m]);
}
}
上述代码可以进行优化到一维
- v代表第i件物品的体积(面值)
f[i][j] = f[i-1][j] + f[i-1][j-v]+f[i-1][j-2v]+...+f[i-1][j-kv])
f[i][j-v] = f[i-1,[j-v]+f[i-1][j-2v]+...+f[i-1][j-kv])
- 因此:
f[i][j] = f[i-1][j]+f[i][j-v])
- 去掉一维状态计算方程为:
f[j] = f[j] + f[j-v]
import java.util.Scanner;
public class MonetarySystem {
public static void main(String[] args) {
int M = 10010;
long[] f = new long[M];
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
f[0] = 1;
for (int i = 1; i <= n; i ++ ) {
int v = sc.nextInt();
for (int j = v; j <= m; j ++ )
f[j] += f[j - v];
}
System.out.println(f[m]);
}
}