数的划分

来源:互联网 发布:筑巢软件上班怎么样 编辑:程序博客网 时间:2024/05/17 06:19

题目描述

Level 1

题目描述

把正整数N分解成M个正整数的和,即使M个数相同但顺序不同也认为是不同的方案,要求总方案数。如3=1+2跟3=2+1是两个不同的方案。

输入

第一行包含两个整数N和M(1<=M<=N<=50)。

输出

输出一个数表示方案数。

样例输入

3 2

样例输出

2

数据范围限制

1<=M<=N<=50

Level 2

题目描述

把正整数N分解成M个非负整数的和,即使M个数相同但顺序不同也认为是不同的方案,要求总方案数。如3=1+2跟3=2+1是两个不同的方案。

输入

第一行输入两个整数(1<=M<=N<=30)。

输出

输出一个整数表示方案数。

样例输入

2 3

样例输出

6

数据范围限制

1<=M<=N<=30

Level 3

题目描述

把正整数N分解成M个正整数的和,M个加数相同但顺序不同认为是相同的方案,要求总方案数。如3=1+2跟3=2+1是两个相同的方案。

输入

第一行输入两个整数N,M(1<=M<=N<=50)。

输出

输出一个整数表示方案数。

样例输入

5 3

样例输出

2

数据范围限制

1<=M<=N<=50

分析

level 1

搜索

拿到这道题我们通常会先用搜索的角度看待本题,我们可以分层依次枚举1-(n-1),再将每一组结果用sum累加。总方案累加的条件即为sum==n且枚举个数等于m.
代码实现如下

#include<iostream>#include<cstdio>using namespace std;int sum,m,n,cont;int a[105];void c(int q){    for(int i=1;i<=m;i++)        if(sum+i<=m&&q<=n)        {            a[q]=i;            sum+=a[q];            if(sum==m&&q==n){cont++;}            c(q+1);            sum-=a[q];        }}int main(){    cont=0;    scanf("%d%d",&m,&n);    c(1);    printf("%d\n",cont);}

然而。。时间复杂度已经达到了O(mn)。也就是说输入50 50时需要循环2500次!很显然,这是超时的,因此需要用其他算法改进。

递推–记忆化搜索

我们从新整理思路,从生活实际的角度开始思考。
本题非常类似放苹果,因此我们可以以1为单位,将m中的一个一看成一个盘子,n中的一个一看成一个苹果,也就是说将n个苹果放入m个盘子里,求它在考虑顺序时的不同分法。相信大家都学过,像这种题可以用夹板法求解,因此我们可以轻松求出递推公式
F[i][j]=ij+1k=1F[ik][j1]|(ik>=1)
边界条件是F[n][1]=1,f[n][m]=1(n=m),F[0][m]=0
代码实现如下

#include<iostream>#include<cstdio>using namespace std;long long f[55][55];long long put(int n,int m){    if(m==1)return 1;    if(m==n)return 1;    if(n<1)return 0;    if(f[n][m])return f[n][m];    long long s=0,t=n-m+1;      for(int i=1;i<=t;i++)        s+=put(n-i,m-1);    return f[n][m]=s;}int main(){    int s1,s2;    scanf("%d%d",&s1,&s2);    printf("%lld\n",put(s1,s2));}

这么一来这道题就AC了,但是这种方法仍然不是最优的,还可以变得更简单。

递推升级–杨辉三角

仔细观察下面这个杨辉三角。
杨辉三角
有没有发现与本题的结果有相似之处呢?例如3行2列的数字2,实际上就是F[3][2]的答案!由此,我们又可以推出一个更简化的递推公式
F[i][j]=f[i1][j]+F[i1][j1]
边界条件只有F[1][1]=1
代码实现如下

#include<iostream>#include<cstdio>using namespace std;long long a[55][55],m,n;int main(){    int s1,s2;    scanf("%d%d",&s1,&s2);    a[1][1]=1;    for(n=2;n<=s1;n++)        for(m=1;m<=n;m++)            a[n][m]=a[n-1][m]+a[n-1][m-1];    printf("%lld\n",a[s1][s2]);}

level 2

经过上一级的“长篇大论”,这一级就要简单许多了。
我们仍然从杨辉三角形讲起。这道题多了一个可以拆的数的存在–0。但也导致了n不一定会大于等于m。因此,这次的图不在是一个三角形了,而是一个长方形。
level 2-1 杨辉三角变形
仍然观察本图,有没有发现与杨辉三角,与本题有很大的相似之处?
通过观察,我们会发现任意一点都等于它左边和上方两数之和。
因此,我们又可以推出递推公式
F[i][j]=F[i-1][j]+F[i][j-1]
边界是 F[1][j]=j,F[i][1]=1
代码实现如下

#include<iostream>#include<cstdio>using namespace std;long long a[55][55],m,n;int main(){    int s1,s2;    scanf("%d%d",&s1,&s2);    for(int j=1;j<=s2;j++)a[1][j]=j;    for(int j=1;j<=s1;j++)a[j][1]=1;    for(n=2;n<=s1;n++)        for(m=2;m<=s2;m++)            a[n][m]=a[n-1][m]+a[n][m-1];    printf("%lld\n",a[s1][s2]);}

level 3

题目又升级了!现在题目要求不考虑排列顺序了,那么,我么是否能用“图”解出来呢?
答案是肯定的,但是本题的规律复杂,数据范围小,并不如传统递归容易,如下图
level 3-1 杨辉三角变形
与放苹果的递归相似,只需略加修改即可。
代码实现如下

#include<iostream>#include<cstdio>using namespace std;int a[55][55],m,n;int main(){    int s1,s2;    for(n=0;n<=50;n++)        {            for(m=0;m<=50;m++)            {                if(m>n)a[n][m]=0;                 else if(m==1)a[n][m]=1;                else if(n==0)a[n][m]=0;                else a[n][m]=a[n-m][m]+a[n-1][m-1];            }        }    scanf("%d%d",&s1,&s2);    printf("%d\n",a[s1][s2]);}

总结

这三道题总的来说都是通过递推,递归完成,但都可以借助图来帮助思维简化递推公式。此外,如果有更好的方法,可以在评论区进行讨论哦