参考http://blog.youkuaiyun.com/viphong/article/details/48110525
题目: 有n种物品, 第i种物品有a个. 不同种类的物品可以互相区分, 但相同种类的无法区分.
从这些物品中取出m个, 有多少种取法? 求出数模M的余数.
例如: 有n=3种物品, 每种a={1,2,3}个, 取出m=3个, 取法result=6(0+0+3, 0+1+2, 0+2+1, 1+0+2, 1+1+1, 1+2+0).
dp[i][j]:前i种数,长度为j的个数
dp[i][j]=dp[i-1][j-k](0<=k<=min(j,m[i])):前i-1种数拿了j-k个,也就是假设第i种数拿了k个;
时间复杂度为O(n*m*m)
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;
const int maxn =105;
int a[maxn];
int dp[maxn][maxn];
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
dp[0][0]=1;
for(int i=1;i<=n;i++)
{
dp[i][0]=1;//开始也要置1
for(int j=1;j<=m;j++)
{
int g=min(j,a[i]);
for(int k=0;k<=g;k++)
dp[i][j]+=dp[i-1][j-k];
}
}
printf("%d\n",dp[n][m]);
return 0;
}
时间复杂度还是太高了,我们化简一下
我们可以发现,把右边求和式展开
分两种情况:
情况1:j<=x[i] (即j-1< x[i])
右边展开得到的是dp[i-1][0] dp[i-1][1] dp[i-1][2]….dp[i-1][j] 、 把最后一项拿出来,剩下的j-1项求和,是dp[i][j-1];
也就是 dp[i][j]=dp[i][j-1]+dp[i-1][j];
情况 2: j>x[i]
右边展开得到的是dp[i-1][j-x[i]] dp[i-1][j-x[i]+1] dp[i-1][j-x[i]+2]….dp[i-1][j] 、仿照上面的形式,得到dp[i][j]=dp[i][j-1]+dp[i-1][j] - dp[i-1][j-1-x[i]];
综上,递推式为:
if(j >a[i] )
dp[i][j] = (dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1-a[i]] +M)%M;
else{
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;
const int maxn =105;
int a[maxn];
int dp[maxn][maxn];
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int M;
cin>>M;
dp[0][0]=1;//空集为1
for(int i=1;i<=n;i++)
{
dp[i][0]=1;
for(int j=1;j<=m;j++)
{
if(j>a[i]) dp[i][j]=(dp[i-1][j]-dp[i-1][j-a[i]-1]+dp[i][j-1]+M)%M;
else dp[i][j]=dp[i][j-1]+dp[i-1][j];
}
}
printf("%d\n",dp[n][m]);
return 0;
}