2018 蓝桥杯省赛 B 组模拟赛(五)划分整数

蒜头君特别喜欢数学。今天,蒜头君突发奇想:如果想要把一个正整数 n分解成不多于 k个正整数相加的形式,那么一共有多少种分解的方式呢?

蒜头君觉得这个问题实在是太难了,于是他想让你帮帮忙。

输入格式

共一行,包含两个整数 n(1n300) 和 k(1k300),含义如题意所示。

输出格式

一个数字,代表所求的方案数。

样例输入

5 3

样例输出

5

递推法

根据n和k的关系,考虑下面几种情况:
(1)当n=1时,不论m的值为多少(m>0),只有一种划分,即{1};
(2)当m=1时,不论
 n
的值为多少(n>0),只有一种划分,即{1,1,....1,1,1};
(3)当n=m时,根据划分中是否包含n,可以分为两种情况:
(a)划分中包含n的情况,只有一个,即{n};
(b)划分中不包含n的情况,这时划分中最大的数字也一定比n小,即n的所有(n-1)划分;
因此,f(n,n) = 1 + f(n, n - 1)。
(4)当n时,由于划分中不可能出现负数,因此就相当于f(n,n);
(5)当n>m时,根据划分中是否包含m,可以分为两种情况:
(a)划分中包含
 m
的情况,即{m,{x1,x2,x3,...,xi}},其中{x1,x2,x3,...,xi}的和为n-m,可能再次出现m,因此是(n-m)的m划分,因此这种划分个数为f(n-m,m;
(b)划分中不包含m的情况,则划分中所有值都比m小,即n的(m-1)划分,个数为f(n,m-1;
因此,f(n,m)=f(n-m,m)+f(n,m-1) 。
综合以上各种情况,可以看出,上面的结论具有递归定义的特征,其中(1)和(2)属于回归条件,(3)和(4)属于特殊情况,而情况(5)为通用情况,属于递归的方法,其本质主要是通过减少n或m以达到回归条件,从而解决问题。
详细递推公式描述如下:

递归源码:

#include 
#define  MYDATA long long
const MYDATA MOD = 1000000007;
#define MAXNUM 100005            //最高次数

//递归法求解整数划分
unsigned long GetPartitionCount(int n, int k)
{
    if (n == 1 || k == 1)return 1;
    if (n < k)return (GetPartitionCount(n, n)) % MOD;
    if (n == k)return (1 + GetPartitionCount(n, n - 1)) % MOD;
    else return (GetPartitionCount(n - k, k) + GetPartitionCount(n, k - 1)) % MOD;
}


int main()
{
    int n;
    int k;
    unsigned long count;
    while (1)
    {
        scanf("%d%d", &n,&k);

        count = GetPartitionCount(n, k);
        printf("%d\n", count);
    }
    return 0;
}

这个版本的时间复杂度较高,运行效率很低。

动态规划法

考虑到在上一节使用递归中,很多的子递归重复计算。如在计算 f (10,9)时,划分成为 f (1,9) + f (10,8),进一步划分为 f (1,1)=f (2,8)+f (10,7),接下来转换为f (1,1) +f (2,2)+ f (3,7)+ f (10,6) =3* f (1,1) +1+ f (3,7)+ f (10,6),这样就产生了重复,然而,在递归的时候,每个都需要被计算一遍,因此可见,位于底层的状态,计算的次数也越来越多。这样在时间开销特别大,是造成运算慢的根本原因,比如算120的时候需要3秒中,计算130的时候需要27秒钟,在计算机200的时候....计算10分钟还没计算出来。
鉴于此,我们已经分析出了普通递推关系中存在大量的冗余造成了重复计算,最终导致了运行时间太长,一种自然地想法是能够用一种技巧,以避免重复计算?这里我们使用动态规划的思想进行程序设计。
在上一节中,已经分析了状态转移的方程,因此,我们在求解子问题时,利用标记的思想,首先检查产生的子问题是否在之前被计算过,这是因为对于相同的子问题,得到的结果肯定是一样的,因此利用这一步去掉冗余的操作,极大减少了计算的时间开销。对于没有标记的子问题来说,一定是没有被计算过,这样,在计算完成后,顺便标记一下子问题,以确保以后不用再重复计算。利用这个特性,可以确保所有的划分子问题都无重复,无遗漏的恰巧被计算一次。
动态规划版主要是利用了标记思想进行加速。

以上内容参考百度百科整数分拆。

参考递推公式写动态规划就很简单了:
#include 
using namespace std;
long long dp[500][500];  
int main()  
{  
    int n,k,i,j;  
    cin>>n>>k; 
    for(i = 1; i <= n; i++)  
    {  
        for(j = 1; j <= k; j++)  
        {  
            if(i == 1||j == 1)  
                dp[i][j] = 1;  
            else if(i == j)  
                dp[i][j] = dp[i][i-1] + 1;  
            else if(i < j)  
                dp[i][j] = dp[i][i];  
            else  
                dp[i][j] = dp[i-j][j] + dp[i][j-1];  
        }  
    }  
    cout<

你可能感兴趣的:(【蓝桥杯】)