编程训练——有关计数的dp:划分数

题目描述:

将n个无区别的物品,划分成不超过m组,求出划分方法总数,输出总数模M的余数。
1 ≤ n , m ≤ 1000 1≤n,m≤1000 1n,m1000
2 ≤ M ≤ 10000 2≤M≤10000 2M10000

样例输入:
4 3 10000

将4划分为不超过3项: 1 + 1 + 2 、 2 + 2 、 1 + 3 、 4 1+1+2、2+2、1+3、4 1+1+22+21+34,有4种划分方法,所以输出 4 % 10000 = 4 4\%10000=4 4%10000=4

样例输出
4

算法

用动态规划, d p [ i ] [ j ] dp[i][j] dp[i][j]表示将 j j j划分成不超过 i i i组。

比如说dp[3][7]表示将7划分成不超过3组,划分方法如下:

1 + 1 + 5 1+1+5 1+1+5
1 + 2 + 4 1+2+4 1+2+4
1 + 3 + 3 1+3+3 1+3+3
2 + 2 + 3 2+2+3 2+2+3
2 + 5 + 0 2+5+0 2+5+0
1 + 6 + 0 1+6+0 1+6+0
3 + 4 + 0 3+4+0 3+4+0
7 + 0 + 0 7+0+0 7+0+0

一共有8种划分方法,现在观察上述8种划分,既然是划分成不超过3项,干脆给未满3项的划分补0,使之变成3项。这样一来,上述8种划分方式就被分为不含0的划分和含0的划分。

证 明 1 , 对 于 将 7 划 分 成 不 超 过 3 项 这 个 问 题 , 不 含 0 的 划 分 总 数 等 于 d p [ 3 ] [ 4 ] : 证明1,对于将7划分成不超过3项这个问题,不含0的划分总数等于dp[3][4]: 1730dp[3][4]

对于所有将4划分成不超过3项:1+1+2、2+2+0、1+3+0、4+0+0(补0保证每种划分都是3项),将每种划分中每一项都加1:2+2+3、2+2+1、2+4+1、5+1+1,发现这些都是将7划分成不超过3项的结果中,不含0的划分。
反之,7划分成不超过3项的结果中,所有不含0的划分,都能将每一项减1变成将4划分为不超过3项。
所以, d p [ 3 ] [ 7 ] dp[3][7] dp[3][7]中不含0的划分就等于 d p [ 3 ] [ 4 ] dp[3][4] dp[3][4]

证 明 2 , 对 于 将 7 划 分 成 不 超 过 3 项 这 个 问 题 , 含 0 的 划 分 总 数 等 于 d p [ 2 ] [ 7 ] : 证明2,对于将7划分成不超过3项这个问题,含0的划分总数等于dp[2][7]: 2730dp[2][7]

对于所有将7划分为不超过2项的划分结果,加个0便成为了将7划分为不超过3项的结果。
对于所有将7划分为不超过3项的划分结果中,含0的划分,去掉一个0便得到了将7划分为不超过2项的划分结果。
所以, d p [ 3 ] [ 7 ] dp[3][7] dp[3][7]中含0的划分就等于 d p [ 2 ] [ 7 ] dp[2][7] dp[2][7]

综上所述,递推关系为:
d p [ i ] [ j ] = { d p [ i ] [ j − i ] + d p [ i − 1 ] [ j ] , j ≥ i d p [ i − 1 ] [ j ] , j < i dp[i][j]=\left\{ \begin{aligned} dp[i][j-i]+dp[i-1][j],j≥i\\ dp[i-1][j],j<i \end{aligned} \right. dp[i][j]={dp[i][ji]+dp[i1][j],jidp[i1][j],j<i

初始值为:
d p [ 0 ] [ 非 零 数 ] = 0 , d p [ . . ] [ 0 ] = 1 dp[0][非零数]=0,dp[..][0]=1 dp[0][]=0,dp[..][0]=1
没有任何非零数是0个数的和,数字0无论分成几项之和都只有一种方式。

代码

#include<stdio.h>

#define maxn 1005

int n, m, M;
int dp[maxn][maxn];

int main(){
    while(scanf("%d %d", &n, &m)==2){
        for(int i = 0;i <= m;i++){
            dp[i][0] = 1;
        }
        for(int j = 1;j <= n;j++){
            dp[0][j] = 0;
        }
        scanf("%d", &M);
        for(int i = 1;i <= m;i++){
            for(int j = 1;j <= n;j++){
                if(j>=i){
                    dp[i][j] = dp[i-1][j] + dp[i][j-i];
                }
                else{
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        printf("%d\n", dp[m][n]%M);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值