有关计数的DP之2——多重集组合数(超详细!)

Problem Description:

有n种物品, 第i种物品有a个. 不同种类的物品可以互相区分, 但相同种类的无法区分.

从这些物品中取出m个, 有多少种取法? 求出数模M的余数.

restricted condition:

1 <= n <= 1000
1 <= m <= 1000
1 <= ai <= 1000
2 <= M <= 10000

sample input:

n = 3
m = 3
a = {1, 2, 3}
M = 10000

sample output

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个。
得到初始递推式

(*)递推式
然而这个公式是O(n*m2)的。不够好

不过我们可以发现,把右边求和式展开

分两种情况:

情况1:j<=a[i] (即j-1<a[i])

右边展开得到的是dp[i][j] + dp[i][j-1] +… + dp[i][1] + dp[i][0] 、 把第一项dp[i][j]拿出去,剩下的 j 项求和:
在这里插入图片描述
而我们此情况下将剩下的 j项 求和带回递推式 :

在这里插入图片描述
两式联立,可得

dp[i+1][j] = dp[i][j] + dp[i+1][j-1] ;

情况 2: j>a[i]

右边展开得到的是dp[i][j] + … + dp[i][j-a[i]],为了方便推导(因为 j>ai ,只有 j-1-ai 才会存在=0的情况),增加式子 :+ dp[i][j-1-a[i]] - dp[i][j-1-a[i]] ,即:
在这里插入图片描述

拿去dp[i][j] - dp[i][j-1-a[i]],剩下的 a[i]+1 项求和(最后一步代回了初始递推式)
在这里插入图片描述

两式联立,可得

dp[i+1][j] = dp[i][j] + dp[i+1][j-1] - dp[i][j-1-a[i]] ;


由情况1,情况2整合:
可大致知递推式:

if (j > a[i])
	dp[i+1][j] = dp[i+1][j-1] + dp[i][j] - dp[i][j-1-a[i]]

else if (j <= a[i])
	dp[i+1][j] = dp[i+1][j-1] + dp[i][j]

下面是参考C++代码
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <iostream>
using namespace std;
int n, m;
int a[1005];

int dp[1005][1005];


int main()
{
	while (cin >> n >> m)
	{
		for (int i = 0; i < n; i++)
			// 一个都不取的方法总是有一种
			cin >> a[i];
		int M;

		cin >> M;
		for (i = 0; i <= n; i++)
			dp[i][0] = 1;
		for (i = 0; i < n; i++)
		{
			for (int j = 1; j <= m; j++)
			{
				if (j > a[i])
					//此处+M是防止减法操作得到一个负数, 加一个M不影响结果并保证了答案不为负数。
					dp[i + 1][j] = (dp[i][j] + dp[i + 1][j - 1] - dp[i][j - 1 - a[i]] + M) % M;

				else
					dp[i + 1][j] = dp[i][j] + dp[i + 1][j - 1];
			}
		}
		cout << dp[n][m]) << endl;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值