挑战程序设计竞赛上有这么一道题:
有n个无区别的物品,将它们划分为不超过m组,求出划分方法数模M的余数。
限制条件:
1≤m≤n≤1000
2≤M≤10000
这样的划分被称作n的m划分,dp数组可以这么定义:dp[i][j]= j 的 i 划分的总数。递推关系的难点在于不重复。
因为说是划分为不超过m组,所以可以是划分为 1~m 组;
那么分成两种情况:
① 恰好m组,即每堆的个数都 >= 1
对于这一种情况,我们定义ai 表示第 i 组一共多少个元素,那么 a1 + a2+……+am = 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;
}