day15 1371 货币系统(完全背包问题)

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 1V25,
1 ≤ N ≤ 10000 1≤N≤10000 1N10000
答案保证在 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])等价呢?

  1. 等号右边的f[j]是用来更新现在左边的f[j]的,注意当前外层for循环是第i次循环了,而等号右边的f[j]却是上一层for循环时计算出来的,即外层for循环是i - 1时计算并保存的f[j],也即f[i-1][j]
  2. 等号右边的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]);
    }
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/YouMing_Li/article/details/113869701