1371. 货币系统
给定 V V V 种货币(单位:元),每种货币使用的次数不限。
不同种类的货币,面值可能是相同的。
现在,要你用这 V V V 种货币凑出 N N N 元钱,请问共有多少种不同的凑法。
输入格式
第一行包含两个整数 V V V 和 N N N。
接下来的若干行,将一共输入 V V V 个整数,每个整数表示一种货币的面值。
输出格式
输出一个整数,表示所求总方案数。
数据范围
1 ≤ V ≤ 25 1≤V≤25 1≤V≤25,
1 ≤ N ≤ 10000 1≤N≤10000 1≤N≤10000
答案保证在 l o n g long long l o n g long long范围内。
输入样例:
3 10
1 2 5
输出样例:
10
思路:DP分析
状态表示: 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]]
Java代码
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
//用n种货币凑成m元钱的方案数,方案数可能很大,故用long型
long[][] dp = new long[n + 1][m + 1];
//初始化,用0种货币凑成0元钱的方案数是1,其余dp[0][j]默认是0
//即用0种货币凑成j(j!=0)元钱的方案数是0
dp[0][0] = 1;
for(int i = 1;i <= n;i++){
int v = scanner.nextInt();//当前货币
for(int j = 0;j <= m;j++){
for(int k = 0; k*v <= j; k++){
//当前货币使用0~k次
dp[i][j] += dp[i-1][j-k*v];
}
}
}
System.out.println(dp[n][m]);
}
}
考虑优化
v
代表第i
件物品的体积(面值)
从前i
种货币中选,价值不超过j
的状态表示为:
f[i][j] = f[i-1][j] + f[i-1][j-v]+f[i-1][j-2v]+,,,,,+f[i-1][j-kv])
从前i
种货币中选,价值不超过j-v
的状态表示为:
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])
Java代码
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
//用n种货币凑成m元钱的方案数,方案数可能很大,故用long型
long[][] dp = new long[n + 1][m + 1];
//初始化,用0种货币凑成0元钱的方案数是1,其余dp[0][j]默认是0
//即用0种货币凑成j(j!=0)元钱的方案数是0
dp[0][0] = 1;
for(int i = 1;i <= n;i++){
int v = scanner.nextInt();//当前货币
for(int j = 0;j <= m;j++){
dp[i][j] = dp[i-1][j];
if(j >= v) dp[i][j] += dp[i][j-v];
}
}
System.out.println(dp[n][m]);
}
}
考虑继续优化,变成一维DP
由上面已经得到的f[i][j] = f[i-1][j]+f[i][j-v])
,我们发现f[i][j]
中的外层for
循环为第i
次循环时的值,只需要用到外层for
循环是第i-1
次循环时的值,由此我们可以做一个等价变形将上式变为:
f[j] = f[j] + f[j-v]
那么f[j] = f[j] + f[j-v]
为何就和f[i][j] = f[i-1][j]+f[i][j-v])
等价呢?
- 等号右边的
f[j]
是用来更新现在左边的f[j]
的,注意当前外层for
循环是第i
次循环了,而等号右边的f[j]
却是上一层for
循环时计算出来的,即外层for
循环是i - 1
时计算并保存的f[j]
,也即f[i-1][j]
。 - 等号右边的
f[j-v]
则因为内层for
循环我们是由小到大更新的,而j-v
一定小于j
,故在求f[j]
之前,f[j-v]
就已经被计算出并更新为外层for
循环是第i
次循环时的值了,也即f[i][j-v]
。所以上面f[j] = f[j] + f[j-v]
为何就和f[i][j] = f[i-1][j]+f[i][j-v])
等价。
Java代码
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
//用n种货币凑成m元钱的方案数,方案数可能很大,故用long型
long[] dp = new long[m + 1];
//初始化,用0种货币凑成0元钱的方案数是1,其余dp[0][j]默认是0
//即用0种货币凑成j(j!=0)元钱的方案数是0
dp[0] = 1;
/*for(int i = 1;i <= n;i++){
int v = scanner.nextInt();//当前货币
for(int j = 0;j <= m;j++){
//内层for循环中就一个if语句,我们发现if语句中的条件还可以提到for中,故还可简化为下面的写法
if(j >= v) dp[j] += dp[j-v];
}*/
for(int i = 1;i <= n;i++){
int v = scanner.nextInt();//当前货币
for(int j = v;j <= m;j++){
dp[j] += dp[j-v];
}
}
System.out.println(dp[m]);
}
}