多重集组合数
有 n 种物品,第 i 种物品有 ai 个。不同种类的物品可以相互区分但是相同的种类无法区分。从这些物品中取出 m 个的话,有多少种取法?求出方案数模M的余数。
1<=n<=1000
1<=m<=1000
1<=ai<=1000
2<=M<=10000
样例输入
n=3
m=3
a={ 1,2,3 }
M=10000
样例输出
6 (0+0+3,0+1+2,0+2+1,1+0+2,1+1+1,1+2+0)
涉及知识及算法
为了不重复计数,同一种类的物品最好一次性处理好。我们可以这样定义:
dp[i+1][j]:=从前i种物品中取出j个的组合总数
为了从前i种物品中取出j个,可以从前i-1个物品中取出j-k个,再从第i种物品中取出k个添加进来,所以得到如下递推关系:
dp[i+1][j]=∑k=0 min(j,ai) dp[i][j−k]
复杂度为O(nm^2),不过有
dp[i+1][j]=dp[i][j]+dp[i][j-1]+…+dp[i−1][j−ai] ,
dp[i+1][j−1]=dp[i][j−1]+…+dp[i][j−ai−1]
所以dp[i+1][j]=dp[i][j]+dp[i+1][j-1]-dp[i][j-ai-1]
这样复杂度就下降到O(nm)了。
import java.util.Scanner;
public class Main2{
public static int maxn = 1000;
public static int n,m,M;
public static int[][] dp = new int[maxn+10][maxn+10];
public static int[] a = new int[maxn+10];
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
for(int i=0;i<n;i++){
a[i] = scanner.nextInt();
}
M = scanner.nextInt();
for(int i=-1;i<n;i++){
dp[i+1][0]=1;
}
for(int i=0;i<n;i++){
for(int j=1;j<=m;j++){
for(int k=0;k<=a[i]&&k<=j;k++){
dp[i+1][j] += dp[i][j-k];
}
}
}
System.out.println(dp[n][m]%M);
}
}
/*3
3
1 2 3
10000
*/
笔记:
1.凡是求值的都可以用dp,求最值如最小值,具体值如方法数都可以用dp
设计dp状态一般优先考虑 dp[i+1][j] 表示前 i 种 数 构成 j 的答案数,
然后找和dp[i][j-k]的关系,这个表示前i-1种数的方案数,此时看第 i 个数取几个 ,第 i 个数取k个,则前 i-1 种数 只能是 前 i 种数方案数减去 k 个,所以是 j-k 个。然后,考虑第 i 种数取 k 个的方案数,由于取k个都一样,不同k才不一样,所以累加所有 k 就能从前 i-1 种情况递推出前i种情况的结果。
2.dp一定要记得初始化,初始化一般为好想的特殊值,如i和j分别为0和1的时候,能想多少写多少就行,dp初始化一定要明确情况再初始化,如果模糊不如不初始化不然直接错误,必要的话dp可以就初始化一个值。