整数n划分成k个不为0的部分

题目:洛谷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;
}
原文地址:https://www.cnblogs.com/lylhome/p/13254991.html