双“11”的抉择

题目描述

把钱花完了,所以单身了,单身了所以过双“11”,过双“11”所以把钱花完了。

今年Nova君(三号)照旧过着他暗无天日的“买买买”的双“11”,然而因为囊中羞涩,并不能够太任性。他的购物车中,列满了数不清的商品,共有N件,好多商品居然还不止一件 __(:3 」∠)_ 现在Nova君要做出一个艰难的抉择,他要从所有商品中挑出m件拼成一个订单,请问有多少种凑单的方法呢?求访法数对M的余数。

PS:同一种商品不作区分。

输入

多组测试数据(不超过100组)

每组数据两行,第一行为三个正整数N,m,M,具体意义详见描述,第二行为N个正整数a1,a2,,,an,代表第i个商品的个数

(1<=N,ai,m<=1000,2<=M<=10000)

输出

对于每组数据,输出一行,表示方法总数

输入样例

3 3 10000
1 2 3

输出样例

6
解题思路:

关键词:多重集组合数

1、为了拉不重复计算,最好是同一种物品一次性解决掉,所以定义如下:

dp[i+1][j] : 从前 i+1 种物品中取出 j 个的方案总数

2、状态转移方程:

前 i+1 种物品取出 j 个 = 前 i+1 种物品取出 j-1个 + 前 i 种物品取出 j 个 - 前i种物品中取出 j-1-ai 个.

因为取出 j-1-ai个, 最后需要 j-1个, 则 ai 需要全部取出, 前两个相重复, 则必然全部取出.

递推公式: dp[ i+1 ][ j ] = dp[ i+1 ][ j-1 ] + dp[ i ][ j ] - dp[ i ][ j-1-ai ]     时间复杂度O(nm).

呈上代码:

#include <bits/stdc++.h>

using namespace std;
const int N=1010;
int a[N];
int b[N][N];
int main()
{
    int n,m,M;
    while(scanf("%d%d%d",&n,&m,&M)==3)
    {
        for(int i = 0; i < n; i++)
            scanf("%d",&a[i]);

        for(int i = 0; i <= n; i++)
            b[i][0] = 1;

        for(int i = 0; i < n; i++)
            for(int j = 1; j <= m; j++)
            {
                if(j -1 - a[i] >= 0)
                    b[i+1][j] = (b[i][j] + b[i+1][j-1] - b[i][j-1-a[i]] +M)%M;
                else
                    b[i+1][j] = (b[i][j] + b[i+1][j-1])%M;
            }
        printf("%d
",b[n][m]);
    }

}
原文地址:https://www.cnblogs.com/zpfbuaa/p/4978945.html