划分数-dp-挑战程序设计竞赛

挑战程序设计竞赛上有这么一道题:

有n个无区别的物品,将它们划分为不超过m组,求出划分方法数模M的余数。

限制条件:

1≤m≤n≤1000

2≤M≤10000



这样的划分被称作n的m划分,dp数组可以这么定义:dp[i][j]= j 的 i 划分的总数。递推关系的难点在于不重复。

因为说是划分为不超过m组,所以可以是划分为 1~m 组;

那么分成两种情况:

① 恰好m组,即每堆的个数都 >= 1

对于这一种情况,我们定义a表示第 i 组一共多少个元素,那么 a1 + a2+……+a= n

因为我们要用到dp, dp的意义是需要用的之前的结果,那么我们如果从这m堆里面,每堆都取出1个元素(注意题目说明了这n个元素无区别),就相当于是n-m的m划分,这个方案数和 n的m划分 是一样的;所以 dp[i][j] = dp[i][j – i]


② 少于m组,即这m堆中,有的堆里面元素个数是0

假如这m堆中有一堆元素个数是0,那么就相当于是 n 的 m-1 划分。因为这种情况至少有1堆的元素个数是0,所以dp[i][j] = dp[i – 1][j] 。因为我们总之要枚举m,能求出 j 对应的1~m组划分的方案,(分1组分两组分三组…………)依次枚举,借助前一个状态求当前状态。

以上思路参考 http://www.hankcs.com/program/m-n-recursive-division.html


如果上面的说法还是不能理解,我写着写着突发奇想,想到一种新的思路;

我们要枚举的是 一共划分成了多少个组。

当前这个第i组 有两种情况,

① 组内无元素, 就相当于是 j 个元素划分成 i-1 组的方案数,即 j 的 i-1 划分,此时 dp[i][j] = dp[i-1][j];

② 当前这一组有元素,就是 j 的 i 划分 ,怎么借助前一种状态来求解当前状态呢? 我们沿用之前那种思路的,如果从这m堆里面,每堆都取出1个元素(注意题目说明了这n个元素无区别),就相当于是n-m的m划分,这个方案数和 n的m划分 是一样的,所以dp[i][j] = dp[i][j – i];


毕竟我也很迷,只能隐隐约约说出那么点自己的理解也不知道对不对,如果第三次啃这个地方的时候能完全理解再来改写

总之综上, dp[i][j] = dp[i][j – i] + dp[i-1][j]



代码

#include <iostream>
#include <cstdio>
#include <map>
#include <cmath>
#include <string.h>
#include <algorithm>
#include <set>
#include <sstream>
#include <vector>
#include <queue>
#include <stack>
using namespace std;

const int maxn = 1000+10;
const int INF = 0x3f3f3f3f;
int dp[maxn][maxn];

int main()
{
    int n,m,M;

    scanf("%d%d%d",&n,&m,&M);
    dp[0][0] = 1;
    for(int i = 1;i<=m;++i){
        for(int j = 0; j<=n; ++j){
            if(j-i >= 0){
                dp[i][j] = dp[i][j-i]%M + dp[i-1][j];///为什么只对第一个取余就够了?
            }
            else{
                dp[i][j] = dp[i-1][j];
            }
            printf("%d %d: %d\n",i,j,dp[i][j]);
        }
    }


    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值