题目描述:
现在有n个相同火柴,将这些火柴分成若干堆,问所有的分法总数。
题目分析:
初见题目第一反应是采用组合数学的方法来计算,有种叫Stirling数的组合数。他表示将n个不同物品分到k堆里,注意这里是n个不同物品,所以不能采用stirling数。
那么我们简单的做几个尝试好了,假设n=4,那么可以分如下几种:
4
1,3 和 3,1算一种
2,2
1,1,2 和 1,2,1 和 2,1,1算一种
1,1,1,1
这5个序列看似没有规律,但是我们还是能找出规律的,(1)中最大堆的根数是4,其他堆的总和是0;(2)中最大堆的根数是3,
其他堆的总和是1;(3)(4)中最大堆的根数是2,其他堆的总和是2,其他堆中的最大也不超过2;(5)中最大堆的根数是1,其他堆的
根数的总和是3,其他堆的最大根数也不超过1。
所有的情况综合起来就是:4个火柴分堆,有可能最大堆的根数是4,其他堆的总和是0,最大根数也是4。或者最大堆的根数不是4
而是3,就属于n个火柴分堆,但最大堆的根数不超过3这种情况了。然后依次类推。
所以可以总结递推公式是dp(n,k) = dp(n-k,k) + dp(n,k-1)。这里n是火柴总数,k表示分堆时最大堆的根数是k。dp(n,k)表示所有分
法的总数,dp(n-k,k)表示确实有分出去一个堆,该堆的根数是k的所有分法,那么剩下还没有分的火柴是n-k根。dp(n,k-1)表示,所有
没有堆的根数到达k(就是所有堆的根数都<=k-1)的所有分法。
那么我们的目标就是求dp(n,n)。在此我们添加边界条件,dp(0,i) = 1,i >= 0;dp(j,0) = 0, j > 0;
伪代码:
for(i = 0; i <= n; .i++)dp[0][i] = 1;
for(j = 1; j <= n ; j++)dp[j][0] = 0;
for(i = 1; i <= n ; i++)
for(j = 1; j <= i; j++)
{
int tmp = 0;
if(i - j > j)tmp = dp[i-j][di-j];
else tmp = dp[i-j][j];
dp[i][j] = tmp + dp[i][j - 1];
}
