AcWing刷题——数字组合 (0-1背包)
题目描述:
给定 N 个正整数 A1,A2,…,AN,从中选出若干个数,使它们的和为 M,求有多少种选择方案。
输入格式
第一行包含两个整数 N和 M。
第二行包含 N个整数,表示 A1,A2,…,AN。
输出格式
包含一个整数,表示可选方案数。
数据范围
1≤N≤100
1≤M≤10000,
1≤Ai≤1000
输入样例:
4 4
1 1 2 2
输出样例:
3
思路:
本题是求有多少种方式,一眼看过去很容易想到是用动态规划来解题,而且是序列型动态规划。
分析:
先看最后一步,题目让我们求从给定的N个数中选出若干个数来,使其之和为M的方式有多少种,此时我们可以关注最后一个数,如果取最后一个数,那么前面必定取的数中之和为M - a[i],那么问题来了。现在我们需要求的就是前N - 1个数中取出若干个数的和为M - a[i]的方式有多少种,可以看的出来我们已经把原问题分解为结构相似的子问题了,根据这种策略一直分解下去,那么最终我们就可以得到一个最小子问题且可以对这个最小子问题进行直接求解,最后再通过这些子问题的解来得到原问题的解即可。
状态:dp[i][j] 表示前i个数字中选出的若干数之和为j的方案有多少种
转移方程:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - a[i]] ,需要满足一定的条件,才可以用这个式子,下面的代码中有。
AC代码:
1 import java.util.*; 2 public class Main{ 3 public static void main(String[] args) { 4 Scanner input = new Scanner(System.in); 5 int N = input.nextInt(); 6 int M = input.nextInt(); 7 int[] arr = new int[N]; 8 for (int i = 0; i < N; i++) { 9 arr[i] = input.nextInt(); 10 } 11 12 13 // 时间复杂度:O(N ^ 2) 14 // 空间复杂度:O(MN) 15 // dp[i][j] 表示前i个数字中选出的若干数之和为j的方案有多少种 序列型动态规划,因为这里我们需要知道前i个数的和,所以就需要开个二维数组 16 int[][] dp = new int[N + 1][M + 1]; 17 18 // 初始化 19 for (int i = 0; i <= M; i++) { 20 dp[0][i] = 0; 21 } 22 23 for (int i = 0; i <= N; i++) { 24 dp[i][0] = 0; 25 } 26 27 28 for (int i = 1; i <= N; i++) { 29 for (int j = 1; j <= M; j++) { 30 if (arr[i - 1] == j) { 31 dp[i][j] = dp[i - 1][j] + 1; 32 continue; 33 } 34 if (arr[i - 1] > j) { 35 dp[i][j] = dp[i - 1][j]; 36 continue; 37 } 38 dp[i][j] = dp[i - 1][j] + dp[i - 1][j - arr[i - 1]]; 39 } 40 } 41 42 System.out.println(dp[N][M]); 43 } 44 45 }