题目:洛谷P1025
解法:
看到这个题目,我第一反应是组合数学的知识,题目等价于将n个相同的物品(数字1)分成k堆,求方案数。
这道题的解法在高中的排列组合肯定用到过,但是想不起来了。
方法如下:
首先,要保证计算过程中相同的方案(每堆的数量相同,但顺序不同)只能计数1次,我采用的是在前一堆的基础上增加后一堆的数量,保证后一堆的数量不小于前一堆,这样每堆的数量是递增的,也就不会担心相同数量的堆顺序不同而重复计数了。
那么,怎样保证后一堆的数量不小于前一堆呢?可以直接考虑后一堆的数量=前一堆的数量+i(i>=0),这样迭代的话需要k-1个循环,感觉不太好做,放弃了。
之后,我采用了少量多次,每次分配的堆数不超过上一次的办法确保后一堆数量不小于前一堆。大体思路是,先分配给k个堆相同的数量i,再分配给h个堆(h<k)相同的数量j(j>=0),令k=h,h更新,继续进行。一直进行下去,直到可分配的数量变为0或者只剩1堆可以分配,那么一种分配方案就产生了。
可以看出,这是一种递归的算法。
每一次操作的h、j不同,产生的解决方案也就不同。
而可以发现,当j>1时,分给h个堆的相同数量j可以分成分给h个堆的相同数量1,分j次。取j>1会使我们漏掉一些分配方案。而取j=0相当于什么都没干。
所以我们将,下次分配的数量定为1,当下次分配的数量j确定了,变量也就剩下次分配的堆数h。
分配的堆数h满足1<=h<=k,要保证下次分配的堆的数量小于当前堆的数量,那么我们令h=k-下次不打算分配的堆数i(0<=i<k)
大体上的思路搞定了,但是题目要求每堆不为空,而如果第一次分配的数目就小于k,那么是不符合题意的。所以,现将k堆分配数量1,可分配的数量变成n=n-k,堆数k=k,之后再递归地进行上述的过程。
代码如下:
#include <stdio.h> long long int sum = 0;//方案数 void div(int n, int k)//n,k代表可分配的数量和分配的堆数 { if (k < 1 || n < 0)//计算过程中,可能存在这种情况,这样的分配方案是不可行的 return; if (k == 1 || n == 0)//当只剩下一个可以分配的堆或者可分配数量都已经分配完了,代表一种方案产生 { sum++; return; } for (int i = 0; i < k; i++)//i代表下次不分配的堆个数,对不同的i值进行递归 div(n - (k-i), k - i); } int main() { int n = 0, k = 0; scanf("%d", &n); scanf("%d", &k); div(n - k, k);//k堆都分数量1,确保k堆都不为0 printf("%d", sum); return 0; }