多重集组合数

多重集组合数
有 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可以就初始化一个值。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值