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;
}
}